#!/usr/bin/python3 import os import sys import time import json import psutil import signal import logging import requests import argparse import configparser from subprocess import Popen, PIPE from multiprocessing.pool import ThreadPool ### функция для преобразования элемента в список def ini_item(value: str=""): tmp=value.split(',') if len(tmp) > 1: return tmp return value ### функция запуска сервера def server_start(param): return Popen(["/usr/bin/python3", param[0], f"{param[1]} {str(param[2])} {param[3]} {str(param[4])} >/dev/null 2>&1"], stdout=PIPE, stderr=PIPE, shell=False).pid ### функция проверки версии игры def server_version(ver): try: if 'steamcmd_update_check' in cfg_settings and cfg_settings['steamcmd_update_check'] != "": # делаем запрос к api steam result = requests.get(cfg_settings['steamcmd_update_check'] + str(ver)) if result.status_code == 200: result=result.json() print(result) # если версия устарела, возвращаем текущую if not result['response']['up_to_date']: return str(result['response']['required_version']) elif 'steamcmd_news_update_check' in cfg_settings and cfg_settings['steamcmd_news_update_check'] != "": # делаем запрос к api steam result = requests.get(cfg_settings['steamcmd_news_update_check']) if result.status_code == 200: result=result.json()['appnews']['newsitems'][0] # проверяем связана ли новость с обновлением title=str(result['title']).lower() contents=str(result['contents']).lower() if title and contents and \ ('fix' in title or 'fix' in contents or \ 'version' in title or 'version' in contents or \ 'changelog' in title or 'changelog' in contents or \ 'update' in title or 'update' in contents): # если дата новости не равна текущей if str(ver) != str(result['date']): return str(result['date']) except: None # если версия актуальна, или код ответа с ошибкой return True ### главная функция if __name__ == "__main__": sys.stderr = open('/dev/null') # парсим аргументы parser = argparse.ArgumentParser(add_help=False) parser.add_argument("action", choices=["start", "stop", "restart", "status"]) parser.add_argument("config") args = parser.parse_args() # конфигурационный файл серверов c_cfg = configparser.ConfigParser() c_cfg.optionxform = str # для правельного учета регистра c_cfg.read(args.config) cfg_settings={} # пробегаем основные параметры for key, value in c_cfg.items("SETTINGS"): cfg_settings[key]=ini_item(value) # создаем объект класса logging logging.basicConfig(filename=cfg_settings['log'], encoding='utf-8', level=logging.INFO,format='%(asctime)s.%(msecs)03d %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S',) # пытаемся прочитать текущую версию из файла try: with open(cfg_settings['steamcmd_game_version'], "r") as f: c_version = f.read() except FileNotFoundError: c_version="" ## функция запуска сервиса def s_start(c_version, cfg_settings, args_config): print("Start ...") count=0 c_pid=str(os.getppid()) pool = ThreadPool() for game in cfg_settings['modes_game']: for dif in cfg_settings['difficulty_game']: time.sleep(0.5) c=pool.map(server_start, ([os.path.join(cfg_settings['dir_work'],"Daemon") + "/kf2-server.py", count, game, dif, args_config] ,)) c_pid+=f" {c[0]}" count+=1 if game in ['KFGameContent.KFGameInfo_WeeklySurvival', 'KFGameContent.KFGameInfo_VersusSurvival']: break pool.close() pool.join() # пишем в файл pid процессов with open(cfg_settings['pid_kf2'], "w") as f: f.write(str(c_pid)) while True: # проверяем наличие обновлений в цикле version=server_version(c_version) if version != True: # если прилетели обновления, обновляем command=f"echo {cfg_settings['sys_user']} | su --pty - {cfg_settings['sys_user']} -c \"steamcmd +force_install_dir {cfg_settings['dir_server_data']} {cfg_settings['steamcmd_update_key']}\"" with Popen(command, stdout=PIPE, stderr=PIPE, shell=True, user=cfg_settings['sys_user'], group=cfg_settings['sys_group'], ) as process: # запускаем процесс от пользователя steam print("Update ...") logging.info(f"Update: New version - {version}, old - {c_version}") c_version=str(version) process.communicate() # после обновления пишим новую версию в файл with open(cfg_settings['steamcmd_game_version'], "w") as f: f.write(str(version)) # после обновления перезапускаемся s_stop(1) return s_start(c_version, cfg_settings, args_config) time.sleep(3600) # проверяем обновление каждый час ## функция остановки сервиса def s_stop(split: int=0): global c_version,cfg_settings print("Stop ...") with open(cfg_settings['pid_kf2'], "r") as f: c_pid = f.read() c_pid=c_pid.split() for i in range(len(c_pid)-split): try: os.killpg(os.getpgid(int(c_pid[(i+split)])), signal.SIGINT) time.sleep(0.5) except: None # если старт if args.action == "start": try: # проверка на запущенность сервиса, проверяем первый pid with open(cfg_settings['pid_kf2'], "r") as f: c_pid = f.read() c_pid=c_pid.split() if str(os.getppid()) != c_pid[0]: try: # если запущен, выходим os.getpgid(int(c_pid[0])) raise KeyboardInterrupt() except OSError: # если не запущен, запускаем s_start(c_version, cfg_settings, args.config) # если запускаем из терминала s_start(c_version, cfg_settings, args.config) except Exception: # если файл pid не существует, запускаем s_start(c_version, cfg_settings, args.config) # если стоп elif args.action == "stop": s_stop() # если перезапуск elif args.action == "restart": s_stop() time.sleep(1) s_start() # если информация о работе elif args.action == "status": bin_name=os.path.basename(cfg_settings['bin_server']) # проверяем все системные процессы и показываем наши сервера for pid in psutil.pids(): p = psutil.Process(pid) if p.name() == bin_name: print(f"[{pid}] {' '.join(p.cmdline())}") # значение по умолчанию else: parser.print_help()