kf2_server/Daemon/kf2-server.py
2025-03-18 10:06:52 +10:00

165 lines
7.6 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/python3
import os
import sys
import signal
import shutil
import logging
import tempfile
import configparser
from subprocess import Popen, PIPE
### функция перехвата сигнала от системы
def receiveSignal(signalNumber, frame):
print(f"I'm killed - {os.getpid()}")
raise KeyboardInterrupt()
### функция для преобразования элемента в список
def ini_item(value: str=""):
tmp=value.split(',')
if len(tmp) > 1:
return tmp
return value
### функция вычисления номера порта
def set_port(value: list, c: int):
if len(value) > 1:
try:
factor=int(value[2])
except Exception:
factor=1
port=int(value[0])+1*factor*c
if int(value[1])>int(value[0]) and port<=int(value[1]):
return port
elif c<int(value[1]):
return port
else:
print(f"I'm killed - {os.getpid()}")
logging.error("You have exceeded the server port limit")
raise KeyboardInterrupt()
return value
### функция запуска сервера
def daemon_server(count:int, game:str, dif:int, config:str):
try:
server_id=f"#{str(count+1)}" # id сервера (используем для именования папки и добавляем к имени сервера)
# конфигурационный файл серверов
c_cfg = configparser.ConfigParser()
c_cfg.optionxform = str # для правельного учета регистра
c_cfg.read(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',)
# путь к папке с конфигурацией сервера
c_dir_ini=os.path.join(cfg_settings['dir_server_data'], f"KFGame/Config/{server_id}")
try:
# удаляем старый ln
shutil.rmtree(c_dir_ini, True)
os.remove(c_dir_ini)
except FileNotFoundError:
None
# создаём временную дерриктори под конфиг
tmp_dir=tempfile.TemporaryDirectory()
# изменяем владельца
shutil.chown(tmp_dir.name, cfg_settings['sys_user'], cfg_settings['sys_group'])
# создаем ln в папку с сервером
os.symlink(tmp_dir.name, c_dir_ini)
# пробегаем дополнительные параметры конфигурации сервера
for key1 in c_cfg.sections():
if key1 != "SETTINGS":
# путь к файлу inic_dir_ini
c_file_ini=f"{c_dir_ini}/{key1}"
# если в теге есть параметры то анализируем
c_key=c_cfg.items(key1)
if len(c_key) > 0:
c_ini = configparser.ConfigParser(strict=False)
c_ini.optionxform = str # для правельного учета регистра
# пробегаем парметры деррективы
for key2, value in c_key:
# разделяем ключ и значение
c_param=ini_item(key2)
# изменяем параметр
if not c_param[0] in c_ini:
c_ini[c_param[0]] = {}
# дописываем индекс в название сервера
if c_param[1] == 'ServerName' or c_param[1] == 'ClanMotto':
value+=f" {server_id}"
# обработка мараметра динамической конфигурации
if c_param[1] == 'bUsedForTakeover':
if int(value) > 0 and count < int(value):
value="FALSE"
else:
value="TRUE"
c_ini[c_param[0]][c_param[1]] = value
# пишем изменения в файл
with open(c_file_ini, "w") as f:
c_ini.write(f)
c_ini.clear()
# запускаем бинарник сервера
command=f"{cfg_settings['bin_server']} kf-bioticslab?Game={game}?Difficulty={dif} -Port={set_port(cfg_settings['port_game'], count)} -QueryPort={set_port(cfg_settings['port_query'], count)} -WebAdminPort={set_port(cfg_settings['port_webadmin'], count)} -ConfigSubDir={server_id}" # команды запуска
while True:
with Popen(command, stdout=PIPE, stderr=PIPE, shell=True, user=cfg_settings['sys_user'], group=cfg_settings['sys_group'], ) as process: # запускаем процесс от пользователя steam
logging.info(f"Start: {command}")
process.communicate() # ждём, пока работает процеес и есть вывод в терминал
logging.info(f"Stop: {command}")
except KeyboardInterrupt:
None
except Exception as err:
logging.info(f"Unexpected termination: {err}")
# при завершение подчищаем за собой
tmp_dir.cleanup()
try:
shutil.rmtree(c_dir_ini, True)
os.remove(c_dir_ini)
except FileNotFoundError:
None
# убиваемся
logging.info(f"Stop: {command}")
os.killpg(os.getpgid(process.pid), signal.SIGINT)
### главная функция
if __name__ == '__main__':
sys.stderr = open('/dev/null')
# ключи асоциативного массива
c_args_name=['script', 'count', 'game', 'dif', 'config']
# если 2 аргумента, вызов из run скрипта
if len(sys.argv) == 2:
c_args_value=sys.argv[1].split()
c_args_value.insert(0, sys.argv[0])
else:
# если 4 аргумента, вызов из shell
c_args_value=sys.argv
# проверяем кол-во передынных параветров вызова
if len(c_args_value) <= 4:
print("Incorrect number of arguments")
raise KeyError()
# получаем ассоциативный массив аргументов
c_args=dict(zip(c_args_name, c_args_value))
# перехватываем сигналы от системы
signal.signal(signal.SIGHUP, receiveSignal)
#signal.signal(signal.SIGINT, receiveSignal)
signal.signal(signal.SIGQUIT, receiveSignal)
signal.signal(signal.SIGILL, receiveSignal)
signal.signal(signal.SIGTRAP, receiveSignal)
signal.signal(signal.SIGABRT, receiveSignal)
signal.signal(signal.SIGBUS, receiveSignal)
signal.signal(signal.SIGFPE, receiveSignal)
#signal.signal(signal.SIGKILL, receiveSignal)
signal.signal(signal.SIGUSR1, receiveSignal)
signal.signal(signal.SIGSEGV, receiveSignal)
signal.signal(signal.SIGUSR2, receiveSignal)
signal.signal(signal.SIGPIPE, receiveSignal)
signal.signal(signal.SIGALRM, receiveSignal)
signal.signal(signal.SIGTERM, receiveSignal)
# запускаем экземпляр сервера
daemon_server(int(c_args['count']), c_args['game'], int(c_args['dif']), c_args['config'])