Автоматизация поднятия сервера после падения.

Moderator: Telakh


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Автоматизация поднятия сервера после падения.

Post by DiWorm » 29 Sep 2014, 07:15

Добрый день!
Я администратор проекта GameComa.ru и мне надоело поднимать сервер каждый раз после его падения.

Я написал мини-программку для автоматического поднятия сервера.
(мой код не претендует на идеальный, вы всегда можете использовать его за основу чего-то другого).

Code: Select all
using System;
using System.Net.Sockets;
using System.Threading;

namespace LiF_server_cheker
{

    class Program
    {

        public static string ip = "127.0.0.1";
        public static int port = 28000;
        public static string pName = "ddctd_cm_yo_server";
        public static string pPath = "D:\\steam\\servers\\feudals\\ddctd_cm_yo_server.exe";

        static void Main(string[] args)
        {
            Connect(ip, port, 0x0E);
        }
        public static int count = 0;
       

        static void Connect(String server, Int32 port, byte message)
        {
           
            while (true)
            {
                string time = DateTime.Now.ToString();
                Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                client.SendTimeout = 2000;
                client.ReceiveTimeout = 2000;
                try
                {
                    client.Connect(server, port);
                        byte[] data = new byte[1] { message };
                        int bytes = client.Send(data);
                        Console.WriteLine(time + " Sended {0} bytes", bytes);
                        byte[] buffer = new byte[256];
                        bytes = client.Receive(buffer);
                        Console.WriteLine(time + " Received {0} bytes", bytes);
                        if (bytes > 0)
                        {
                            Console.Write(time + " Message: ");
                            for (int i = 0; i < bytes; i++) Console.Write("{0:X} ", buffer[i]);
                            Console.WriteLine();
                            Thread.Sleep(5000);
                        }
                        else
                        {
                            Restart();
                        }
                        client.Close();
                        Thread.Sleep(2000);
                }
                catch(SocketException e)
                {
                    //Console.Write(e);
                    Restart();
                }
                   
            }
        }
        static void Restart()
        {
            string time = DateTime.Now.ToString();
            if (count == 10)
            {
                Console.WriteLine(time + " Server Down!");
                Console.WriteLine(time + " Closing crashed server...");
                string name = pName;
                System.Diagnostics.Process[] etc = System.Diagnostics.Process.GetProcesses();
                foreach (System.Diagnostics.Process anti in etc)
                    if (anti.ProcessName.ToLower().Contains(name.ToLower())) anti.Kill();
                Thread.Sleep(5000);
                Console.WriteLine(time + " Trying to start");
                System.Diagnostics.Process.Start(pPath);
                try
                {
                    string namewf = "WerFault";
                    System.Diagnostics.Process[] werfault = System.Diagnostics.Process.GetProcesses();
                    foreach (System.Diagnostics.Process anti in werfault)
                        if (anti.ProcessName.ToLower().Contains(namewf.ToLower())) anti.Kill();
                }
                catch { }
                count = 0;
                Thread.Sleep(300000);
            }
            else
            {
                Console.WriteLine(time + " Attempt to check: " + count);
                Thread.Sleep(1000);
                count++;
            }
        }
    }
}

,где:

ip - IP-адрес вашего сервера(ипользуйте 127.0.0.1, т.к. приложение умеет работать только локально).
port - игровой порт сервера.
pName - имя exe файла, которое висит в процессах (по умолчанию это ddctd_cm_yo_server !!!БЕЗ .exe!!!).
pPath - путь к файлу запуска сервера(обязательно использовать \\ вместо \ в пути к файлу).


Логика работы:
Отправляем пакет, если получаем 1, то все хорошо - проверяем через 5 секунд.
Если получаем 0(сервер завис), то убиваем процесс сервера и возможное окно об ошибке, ждем 5 минут, проверяем работу.
Если не получен ответ от сервера(сервер упал или какие-то жуткие лаги(например, как на рассвете)), делаем 10 попыток подключения, если подключиться не удалось - перезагружаем сервер, убиваем возможное окно об ошибке.

Как собрать:
Качаем Microsoft Visual Studio Express (бесплатная версия) -> устанавливаем -> создаем консольное приложение C# -> вставляем код -> выбираем Release сборку, жмем F5.
После чего находим собранный файл в папке проекта: Project Name/bin/Release, берем EXE-файл -> вы великолепны :)

О всех найденных ошибках можно и нужно писать в этой теме.

Данный скрипт уже работает на нашем сервере.
В списке мы: GameComa.ru
Либо консольной командой: joinToRemoteServer("88.87.84.203:28000","")
Last edited by DiWorm on 29 Sep 2014, 11:12, edited 1 time in total.


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Re: Автоматизация поднятия сервера после падения.

Post by DiWorm » 29 Sep 2014, 11:11

Выложил допиленный код.


Sm1ly
 
Posts: 7
Joined: 22 Sep 2014, 12:30

Re: Автоматизация поднятия сервера после падения.

Post by Sm1ly » 29 Sep 2014, 11:15

скрипт на павершелле.
запихать в планировщик задач каждые 3 минуты (мой серв быстрее не стартует)

$target="127.0.0.1"
$target_port="28000"
$udpClient=New-Object System.Net.Sockets.UdpClient
$pingBuf = New-Object Byte[] -ArgumentList 1
$pingBuf[0]=14
$udpClient.Connect($target,$target_port)
$udpClient.Client.ReceiveTimeout=2000
$udpClient.Send($pingBuf,1)
$udpRecIP= New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)
try
{
$recv = $udpClient.Receive([ref]$udpRecIP)
}
catch
{
echo "no response in 2000 milliseconds"
Get-Process ddctd_cm_yo_server | kill
Start-Process -FilePath "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\ddctd_cm_yo_server.exe" -ArgumentList "-worldId","1" -WindowStyle Normal -WorkingDirectory "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\"
echo "execed"
exit -1
}
if($recv[0] -eq 0)
{
echo "null received"
}
else
{
echo "service OK"
}


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Re: Автоматизация поднятия сервера после падения.

Post by DiWorm » 29 Sep 2014, 11:19

Sm1ly wrote:скрипт на павершелле.
запихать в планировщик задач каждые 3 минуты (мой серв быстрее не стартует)

$target="127.0.0.1"
$target_port="28000"
$udpClient=New-Object System.Net.Sockets.UdpClient
$pingBuf = New-Object Byte[] -ArgumentList 1
$pingBuf[0]=14
$udpClient.Connect($target,$target_port)
$udpClient.Client.ReceiveTimeout=2000
$udpClient.Send($pingBuf,1)
$udpRecIP= New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)
try
{
$recv = $udpClient.Receive([ref]$udpRecIP)
}
catch
{
echo "no response in 2000 milliseconds"
Get-Process ddctd_cm_yo_server | kill
Start-Process -FilePath "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\ddctd_cm_yo_server.exe" -ArgumentList "-worldId","1" -WindowStyle Normal -WorkingDirectory "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\"
echo "execed"
exit -1
}
if($recv[0] -eq 0)
{
echo "null received"
}
else
{
echo "service OK"
}


3 минуты против 10 секунд ;)
P.S. Пытался осилить на повершеле, решил накодить на шарпе, как-то оно более нежно на нем :)
P.P.S. Вашу идею возьму для вечнопадающего старбаунда :)
P.P.P.S. И да, если онлайн высокий и по утрам есть сильные лаги на рассвете, ваш скрипт может сработать не правильно.


Nikebl
 
Posts: 5
Joined: 19 Sep 2014, 17:08

Re: Автоматизация поднятия сервера после падения.

Post by Nikebl » 29 Sep 2014, 12:18

Скину свой костыль под Linux для рестарта виртуальной машины с сервером lif. У меня используется KVM, но можно заменить команду на рестарт для любой другой системы виртуализации.
Сам скрипт:

Code: Select all
#!/bin/bash
host="192.168.1.106"
port="28000"

test="$(echo -ne '\x0e' | nc -v -u -w 2 $host $port)"
if [[ -z $test ]]; then
        sleep 60;
        test2="$(echo -ne '\x0e' | nc -v -u -w 2 $host $port)"
        if [[ -z $test2 ]]; then
                virsh reboot win7; #command to reboot vm
                echo server rebooted `date`  >> /tmp/lifwd.log
                sleep 420;
        fi
fi




Запихать в cron как:
Code: Select all
*/5 * * * * flock -w 180 -x /tmp/lifwd.lock -c Путь_до_скрипта

Что бы исключить повторный вызов скрипта во время ребута.


Karsidar
Beta Tester
 
Posts: 43
Joined: 29 Sep 2014, 21:20

Re: Автоматизация поднятия сервера после падения.

Post by Karsidar » 30 Sep 2014, 13:50

DiWorm wrote:Добрый день!
Я администратор проекта GameComa.ru и мне надоело поднимать сервер каждый раз после его падения.

Я написал мини-программку для автоматического поднятия сервера.
(мой код не претендует на идеальный, вы всегда можете использовать его за основу чего-то другого).

Code: Select all
using System;
using System.Net.Sockets;
using System.Threading;

namespace LiF_server_cheker
{

    class Program
    {

        public static string ip = "127.0.0.1";
        public static int port = 28000;
        public static string pName = "ddctd_cm_yo_server";
        public static string pPath = "D:\\steam\\servers\\feudals\\ddctd_cm_yo_server.exe";

        static void Main(string[] args)
        {
            Connect(ip, port, 0x0E);
        }
        public static int count = 0;
       

        static void Connect(String server, Int32 port, byte message)
        {
           
            while (true)
            {
                string time = DateTime.Now.ToString();
                Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                client.SendTimeout = 2000;
                client.ReceiveTimeout = 2000;
                try
                {
                    client.Connect(server, port);
                        byte[] data = new byte[1] { message };
                        int bytes = client.Send(data);
                        Console.WriteLine(time + " Sended {0} bytes", bytes);
                        byte[] buffer = new byte[256];
                        bytes = client.Receive(buffer);
                        Console.WriteLine(time + " Received {0} bytes", bytes);
                        if (bytes > 0)
                        {
                            Console.Write(time + " Message: ");
                            for (int i = 0; i < bytes; i++) Console.Write("{0:X} ", buffer[i]);
                            Console.WriteLine();
                            Thread.Sleep(5000);
                        }
                        else
                        {
                            Restart();
                        }
                        client.Close();
                        Thread.Sleep(2000);
                }
                catch(SocketException e)
                {
                    //Console.Write(e);
                    Restart();
                }
                   
            }
        }
        static void Restart()
        {
            string time = DateTime.Now.ToString();
            if (count == 10)
            {
                Console.WriteLine(time + " Server Down!");
                Console.WriteLine(time + " Closing crashed server...");
                string name = pName;
                System.Diagnostics.Process[] etc = System.Diagnostics.Process.GetProcesses();
                foreach (System.Diagnostics.Process anti in etc)
                    if (anti.ProcessName.ToLower().Contains(name.ToLower())) anti.Kill();
                Thread.Sleep(5000);
                Console.WriteLine(time + " Trying to start");
                System.Diagnostics.Process.Start(pPath);
                try
                {
                    string namewf = "WerFault";
                    System.Diagnostics.Process[] werfault = System.Diagnostics.Process.GetProcesses();
                    foreach (System.Diagnostics.Process anti in werfault)
                        if (anti.ProcessName.ToLower().Contains(namewf.ToLower())) anti.Kill();
                }
                catch { }
                count = 0;
                Thread.Sleep(300000);
            }
            else
            {
                Console.WriteLine(time + " Attempt to check: " + count);
                Thread.Sleep(1000);
                count++;
            }
        }
    }
}

,где:

ip - IP-адрес вашего сервера(ипользуйте 127.0.0.1, т.к. приложение умеет работать только локально).
port - игровой порт сервера.
pName - имя exe файла, которое висит в процессах (по умолчанию это ddctd_cm_yo_server !!!БЕЗ .exe!!!).
pPath - путь к файлу запуска сервера(обязательно использовать \\ вместо \ в пути к файлу).


Логика работы:
Отправляем пакет, если получаем 1, то все хорошо - проверяем через 5 секунд.
Если получаем 0(сервер завис), то убиваем процесс сервера и возможное окно об ошибке, ждем 5 минут, проверяем работу.
Если не получен ответ от сервера(сервер упал или какие-то жуткие лаги(например, как на рассвете)), делаем 10 попыток подключения, если подключиться не удалось - перезагружаем сервер, убиваем возможное окно об ошибке.

Как собрать:
Качаем Microsoft Visual Studio Express (бесплатная версия) -> устанавливаем -> создаем консольное приложение C# -> вставляем код -> выбираем Release сборку, жмем F5.
После чего находим собранный файл в папке проекта: Project Name/bin/Release, берем EXE-файл -> вы великолепны :)

О всех найденных ошибках можно и нужно писать в этой теме.

Данный скрипт уже работает на нашем сервере.
В списке мы: GameComa.ru
Либо консольной командой: joinToRemoteServer("88.87.84.203:28000","")


мм у вас сервер на win основе работает ?
если да , то все можно автоматизировать через
SC create .
Тоесть обычный мелкософтный сервис.


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Re: Автоматизация поднятия сервера после падения.

Post by DiWorm » 30 Sep 2014, 14:46

Karsidar wrote:
DiWorm wrote:Добрый день!
Я администратор проекта GameComa.ru и мне надоело поднимать сервер каждый раз после его падения.

Я написал мини-программку для автоматического поднятия сервера.
(мой код не претендует на идеальный, вы всегда можете использовать его за основу чего-то другого).

Code: Select all
using System;
using System.Net.Sockets;
using System.Threading;

namespace LiF_server_cheker
{

    class Program
    {

        public static string ip = "127.0.0.1";
        public static int port = 28000;
        public static string pName = "ddctd_cm_yo_server";
        public static string pPath = "D:\\steam\\servers\\feudals\\ddctd_cm_yo_server.exe";

        static void Main(string[] args)
        {
            Connect(ip, port, 0x0E);
        }
        public static int count = 0;
       

        static void Connect(String server, Int32 port, byte message)
        {
           
            while (true)
            {
                string time = DateTime.Now.ToString();
                Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                client.SendTimeout = 2000;
                client.ReceiveTimeout = 2000;
                try
                {
                    client.Connect(server, port);
                        byte[] data = new byte[1] { message };
                        int bytes = client.Send(data);
                        Console.WriteLine(time + " Sended {0} bytes", bytes);
                        byte[] buffer = new byte[256];
                        bytes = client.Receive(buffer);
                        Console.WriteLine(time + " Received {0} bytes", bytes);
                        if (bytes > 0)
                        {
                            Console.Write(time + " Message: ");
                            for (int i = 0; i < bytes; i++) Console.Write("{0:X} ", buffer[i]);
                            Console.WriteLine();
                            Thread.Sleep(5000);
                        }
                        else
                        {
                            Restart();
                        }
                        client.Close();
                        Thread.Sleep(2000);
                }
                catch(SocketException e)
                {
                    //Console.Write(e);
                    Restart();
                }
                   
            }
        }
        static void Restart()
        {
            string time = DateTime.Now.ToString();
            if (count == 10)
            {
                Console.WriteLine(time + " Server Down!");
                Console.WriteLine(time + " Closing crashed server...");
                string name = pName;
                System.Diagnostics.Process[] etc = System.Diagnostics.Process.GetProcesses();
                foreach (System.Diagnostics.Process anti in etc)
                    if (anti.ProcessName.ToLower().Contains(name.ToLower())) anti.Kill();
                Thread.Sleep(5000);
                Console.WriteLine(time + " Trying to start");
                System.Diagnostics.Process.Start(pPath);
                try
                {
                    string namewf = "WerFault";
                    System.Diagnostics.Process[] werfault = System.Diagnostics.Process.GetProcesses();
                    foreach (System.Diagnostics.Process anti in werfault)
                        if (anti.ProcessName.ToLower().Contains(namewf.ToLower())) anti.Kill();
                }
                catch { }
                count = 0;
                Thread.Sleep(300000);
            }
            else
            {
                Console.WriteLine(time + " Attempt to check: " + count);
                Thread.Sleep(1000);
                count++;
            }
        }
    }
}

,где:

ip - IP-адрес вашего сервера(ипользуйте 127.0.0.1, т.к. приложение умеет работать только локально).
port - игровой порт сервера.
pName - имя exe файла, которое висит в процессах (по умолчанию это ddctd_cm_yo_server !!!БЕЗ .exe!!!).
pPath - путь к файлу запуска сервера(обязательно использовать \\ вместо \ в пути к файлу).


Логика работы:
Отправляем пакет, если получаем 1, то все хорошо - проверяем через 5 секунд.
Если получаем 0(сервер завис), то убиваем процесс сервера и возможное окно об ошибке, ждем 5 минут, проверяем работу.
Если не получен ответ от сервера(сервер упал или какие-то жуткие лаги(например, как на рассвете)), делаем 10 попыток подключения, если подключиться не удалось - перезагружаем сервер, убиваем возможное окно об ошибке.

Как собрать:
Качаем Microsoft Visual Studio Express (бесплатная версия) -> устанавливаем -> создаем консольное приложение C# -> вставляем код -> выбираем Release сборку, жмем F5.
После чего находим собранный файл в папке проекта: Project Name/bin/Release, берем EXE-файл -> вы великолепны :)

О всех найденных ошибках можно и нужно писать в этой теме.

Данный скрипт уже работает на нашем сервере.
В списке мы: GameComa.ru
Либо консольной командой: joinToRemoteServer("88.87.84.203:28000","")


мм у вас сервер на win основе работает ?
если да , то все можно автоматизировать через
SC create .
Тоесть обычный мелкософтный сервис.


там выше в теме привели пример через поше, но мне реализация не нравится.


Sm1ly
 
Posts: 7
Joined: 22 Sep 2014, 12:30

Re: Автоматизация поднятия сервера после падения.

Post by Sm1ly » 01 Oct 2014, 11:18

DiWorm wrote:
Sm1ly wrote:скрипт на павершелле.
запихать в планировщик задач каждые 3 минуты (мой серв быстрее не стартует)

$target="127.0.0.1"
$target_port="28000"
$udpClient=New-Object System.Net.Sockets.UdpClient
$pingBuf = New-Object Byte[] -ArgumentList 1
$pingBuf[0]=14
$udpClient.Connect($target,$target_port)
$udpClient.Client.ReceiveTimeout=2000
$udpClient.Send($pingBuf,1)
$udpRecIP= New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)
try
{
$recv = $udpClient.Receive([ref]$udpRecIP)
}
catch
{
echo "no response in 2000 milliseconds"
Get-Process ddctd_cm_yo_server | kill
Start-Process -FilePath "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\ddctd_cm_yo_server.exe" -ArgumentList "-worldId","1" -WindowStyle Normal -WorkingDirectory "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\"
echo "execed"
exit -1
}
if($recv[0] -eq 0)
{
echo "null received"
}
else
{
echo "service OK"
}


3 минуты против 10 секунд ;)
P.S. Пытался осилить на повершеле, решил накодить на шарпе, как-то оно более нежно на нем :)
P.P.S. Вашу идею возьму для вечнопадающего старбаунда :)
P.P.P.S. И да, если онлайн высокий и по утрам есть сильные лаги на рассвете, ваш скрипт может сработать не правильно.



я вообще линуксоадмин и этот скрипт помогал мне писать товарищ, вопрос у меня возник.
решил я сегодня поиграть в бф. но у меня скрипт запускается аля через cmd. соответственно бф вылетает в винду (сворачивается окно). может знаете как поправить?
или же иначе, ваш скрипт так делает?
а то пичальбида, хочется поиграть с новым патчем в бф.


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Re: Автоматизация поднятия сервера после падения.

Post by DiWorm » 01 Oct 2014, 12:14

Sm1ly wrote:
DiWorm wrote:
Sm1ly wrote:скрипт на павершелле.
запихать в планировщик задач каждые 3 минуты (мой серв быстрее не стартует)

$target="127.0.0.1"
$target_port="28000"
$udpClient=New-Object System.Net.Sockets.UdpClient
$pingBuf = New-Object Byte[] -ArgumentList 1
$pingBuf[0]=14
$udpClient.Connect($target,$target_port)
$udpClient.Client.ReceiveTimeout=2000
$udpClient.Send($pingBuf,1)
$udpRecIP= New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0)
try
{
$recv = $udpClient.Receive([ref]$udpRecIP)
}
catch
{
echo "no response in 2000 milliseconds"
Get-Process ddctd_cm_yo_server | kill
Start-Process -FilePath "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\ddctd_cm_yo_server.exe" -ArgumentList "-worldId","1" -WindowStyle Normal -WorkingDirectory "C:\Program Files (x86)\Steam\steamapps\common\Life is Feudal Your Own Dedicated Server\"
echo "execed"
exit -1
}
if($recv[0] -eq 0)
{
echo "null received"
}
else
{
echo "service OK"
}


3 минуты против 10 секунд ;)
P.S. Пытался осилить на повершеле, решил накодить на шарпе, как-то оно более нежно на нем :)
P.P.S. Вашу идею возьму для вечнопадающего старбаунда :)
P.P.P.S. И да, если онлайн высокий и по утрам есть сильные лаги на рассвете, ваш скрипт может сработать не правильно.



я вообще линуксоадмин и этот скрипт помогал мне писать товарищ, вопрос у меня возник.
решил я сегодня поиграть в бф. но у меня скрипт запускается аля через cmd. соответственно бф вылетает в винду (сворачивается окно). может знаете как поправить?
или же иначе, ваш скрипт так делает?
а то пичальбида, хочется поиграть с новым патчем в бф.


мой скрипт висит примерно так: https://yadi.sk/i/FZWgZRhibm4VA и жизни не мешет. Только если у вас вылетит сервер с крашем, то скорее всего вендовое окошко с "Ошибка, закрыть?" может свернуть другие приложения, но я не уверен в этом.

Можете оставить тут порт и путь до сервера, я скомпилю файл под вас - проверите.


DiWorm
 
Posts: 90
Joined: 22 Sep 2014, 04:58

Re: Автоматизация поднятия сервера после падения.

Post by DiWorm » 10 Oct 2014, 09:17

Еще 1 простой вариант:

.bat файл поместить в папку с сервером, содержание:
Code: Select all
:loop
start /wait ddctd_cm_yo_server.exe -worldID X
goto loop

где Х - номер мира из папки, по умолчанию 1.

Смысл скрипта: запускать приложение и ждать пока оно не стухнет\не крашнется, после этого повторять цикл запуска.

Но есть нюанс, при краше сервера появляется процесс WerFault, который будет мешать выполнению скрипта.

Вот 4 строчная утилитка, которая будет проверять наличие этого процесса каждые 5 секунд и если он есть - убивать его.

https://yadi.sk/d/3GAD8On9bv9Zh


Mhiz
 
Posts: 24
Joined: 02 Oct 2014, 02:14

Re: Автоматизация поднятия сервера после падения.

Post by Mhiz » 30 Oct 2014, 00:42

может кому полезно будет.

исходник DiWorm дописал только, что бы авторестарт работал с тем процессом который он сам запустил, если у вас запущено несколько серверов в оригинале, если 1 упал потянет за собой все и начнется карусель ..

---
изменения надо сначало запустить утилиту, что бы она сама запустила процесс
---
так же вынес параметр для выбора мира.

таким образом я скомпилировал 2 варианта для 2 миров своих отлично работают.

Spoiler

Return to Обсуждения на русском языке

cron