Настройка выделенного сервера Source под Linux, часть 3

  • Установка MetaMod и SourceMod
    • Установка MetaMod: Source
    • Установка SourceMod
  • Автозапуск игровых серверов
  • Логи
    • Логи cron
    • Логи клиента Steam
    • Логи веб-сервера
    • Логи SourceMod
    • Логи игровых серверов
    • Логи сервера статистики
  • Привязка к Steam и QuickPlay
    • Серверная учётная запись
    • QuickPlay

Установка MetaMod и SourceMod

Пришла пора внести свежую струю в процесс настройки совместного сосуществования наших серверов, а именно — добавить MetaMod и SourceMod.


Установка MetaMod: Source


Сначала установим плагин MetaMod: Source, который никаких игровых функций не добавляет, а лишь обеспечивает интерфейс между движком Source, игрой и другими плагинами.


Домашняя страница — http://www.metamodsource.net/


Скачиваем последнюю версию и распаковываем архив.


   $ wget http://cdn.probablyaserver.com/sourcemod/mmsource-1.10.6-linux.tar.gz
   $ tar -xvzf mmsource-1.10.6-linux.tar.gz -C ~/tf2/tf

В каталоге ~/tf2/tf будет создан подкаталог addons (в него мы будем устанавливать и другие моды), а в нём каталог metamod с рядом файлов для различных Source игр, в том числе и для Team Fortress 2:


   addons/
   addons/metamod.vdf
   ...
   addons/metamod/bin/metamod.2.tf2.so
   addons/metamod/bin/server.so
   ...
   addons/metamod/metaplugins.ini

Игровой сервер srcds при запуске проверяет наличие каталога ~/tf2/tf/addons, ищёт в нём *.vdf файлы, в которых в параметре «file» должны быть указаны пути к библиотекам плагинов. В нашем случае, как раз в таком файле ~/tf2/tf/addons/metamod.vdf подключен плагин MetaMod: Source как «addons/metamod/bin/server» (путь указывается относительно ~/tf2/tf/).


Теперь мы установим SourceMod и подключим его к MetaMod.


Установка SourceMod


Домашняя страница — http://www.sourcemod.net/


Переходим на http://www.sourcemod.net/downloads.php? branch=stable, скачиваем последнюю версию под Linux и распаковываем архив


   $ wget https://sm.alliedmods.net/smdrop/1.8/sourcemod-1.8.0-git5947-linux.tar.gz
   $ tar -xvzf sourcemod-1.8.0-git5947-linux.tar.gz -C ~/tf2/tf

В ~/tf2/tf/addons появился каталог sourcemod, в ~/tf2/tf/cfg/ — каталог sourcemod с файлами конфигурации, а в
~/tf2/tf/addons/metamod — файлик sourcemod.vdf.


   addons/metamod/sourcemod.vdf
   addons/sourcemod/
   addons/sourcemod/scripting/
   addons/sourcemod/configs/core.cfg
   ...
   cfg/sourcemod/sm_warmode_off.cfg
   cfg/sourcemod/sm_warmode_on.cfg
   cfg/sourcemod/sourcemod.cfg

Так как одна инсталляция SourceMod не может одновременно обслуживать несколько серверов, то нам необходимо растиражировать по количеству серверов. В каталоге addons переименовываем sourcemod в sourcemod1 и копируем со всем содержимым в sourcemod2, предварительно для удобства выполнив dos2unix для файлов настроек:


   $ mv ~/tf2/tf/addons/sourcemod ~/tf2/tf/addons/sourcemod1
   $ cd ~/tf2/tf/addons/sourcemod1/configs
   $ dos2unix *.cfg *.ini *.txt
   $ cp -r ~/tf2/tf/addons/sourcemod1 ~/tf2/tf/addons/sourcemod2

MetaMod находит и подключает SourceMod благодаря файлу addons/metamod/sourcemod.vdf. Но этот файл один, а нам надо два разных, для каждого сервера. У MetaMod есть параметр, указывающий на каталог (не файл!) с конфигурацией, поэтому создадим два каталога cfg1 и cfg2 и поместим туда sourcemod.vdf, который и будем править.


   $ mkdir ~/tf2/tf/addons/metamod/cfg{1,2}
   $ dos2unix ~/tf2/tf/addons/metamod/sourcemod.vdf
   $ mv ~/tf2/tf/addons/metamod/sourcemod.vdf ~/tf2/tf/addons/metamod/cfg1/sourcemod.vdf
   $ cp ~/tf2/tf/addons/metamod/cfg1/sourcemod.vdf ~/tf2/tf/addons/metamod/cfg2/sourcemod.vdf

Редактируем эти файлы, исправляя пути к инсталляциям SourceMod (так же, относительно ~/tf2/tf):


~/tf2/tf/addons/metamod/cfg1/sourcemod.vdf:


cfg1/sourcemod.vdf
"Metamod Plugin"
{
    "alias"     "sourcemod"
    "file"      "addons/sourcemod1/bin/sourcemod_mm"
}

~/tf2/tf/addons/metamod/cfg2/sourcemod.vdf (исправляем 1 → 2):


cfg2/sourcemod.vdf
"Metamod Plugin"
{
    "alias"     "sourcemod"
    "file"      "addons/sourcemod2/bin/sourcemod_mm"
}

Для удобства делаем символьные ссылки на каталоги с настройками и логами:


   $ ln -s ~/tf2/tf/addons/metamod/cfg1 ~/cfg/mm1
   $ ln -s ~/tf2/tf/addons/metamod/cfg2 ~/cfg/mm2
   $ ln -s ~/tf2/tf/addons/sourcemod1/configs ~/cfg/sm1
   $ ln -s ~/tf2/tf/addons/sourcemod2/configs ~/cfg/sm2
   $ ln -s ~/tf2/tf/addons/sourcemod1/logs ~/log/sm1
   $ ln -s ~/tf2/tf/addons/sourcemod2/logs ~/log/sm2

Каких-то отдельных команд для запуска MetaMod прописывать не надо — игровой сервер запустит его автоматически, но нам надо указать различные каталоги для загрузки SourceMod.


В файле ~/cfg/autoexec1.cfg для первого сервера дописываем в конец:


autoexec1.cfg
//...

// Load SourceMod instance No 1
mm_basedir addons/metamod/cfg1

Для второго сервера в ~/cfg/autoexec2.cfg аналогично, только вместо «cfg1» → «cfg2»:


autoexec2.cfg
//...

// Load SourceMod instance No 2
mm_basedir addons/metamod/cfg2

Теперь MetaMod знает откуда загружать SourceMod, но последний не знает откуда ему брать свои настройки. Пропишем пути для каждого SourceMod. В отличие от MetaMod, параметры нам придётся указывать аж в командной строке запуска сервера. Ни в autoexec.cfg, ни в server.cfg они не срабатывают.


В скриптах запусках start1.sh и start2.sh, в CMDLINE дописываем:


start1.sh
CMDLINE="...
    +sm_basepath addons/sourcemod1 +sm_corecfgfile addons/sourcemod1/configs/core.cfg \
    ...

Это для первого сервера. Для второго сервера аналогично, только вместо «sourcemod1» → «sourcemod2»:


start2.sh
CMDLINE="...
    +sm_basepath addons/sourcemod2 +sm_corecfgfile addons/sourcemod2/configs/core.cfg \
    ...

Параметр sm_corecfgfile необходим, так как путь к core.cfg не берётся из sm_basepath, что логично было бы предположить.


Для проверки можно запустить первый игровой сервер и когда он полностью запустится, в его консоли ввести (команды выделены угловыми скобками):


   >>> meta version
   Metamod:Source version 1.10.6
   Built from: https://github.com/alliedmodders/metamod-source/commit/9fed12f
   Build ID: 946:9fed12f
   Loaded As: Valve Server Plugin
   Compiled on: Sep 12 2015
   Plugin interface version: 15:14
   SourceHook version: 5:5
   http://www.metamodsource.net/
   >>> mm_basedir
   "mm_basedir" = "addons/metamod/cfg1" ( def. "addons/metamod" )
    singleplayer
    - Metamod:Source Base Folder
   >>> meta list
   Listing 4 plugins:
   [01] SourceMod (1.8.0.5907) by AlliedModders LLC
   [02] TF2 Tools (1.8.0.5907) by AlliedModders LLC
   [03] SDK Hooks (1.8.0.5907) by AlliedModders LLC
   [04] SDK Tools (1.8.0.5907) by AlliedModders LLC
   >>> meta info 1
   Plugin 1 is running.
   Name: "SourceMod" by AlliedModders LLC
   Version: 1.8.0.5907
   Description: Extensible administration and scripting system
   License: See LICENSE.txt
   URL: http://www.sourcemod.net/
   Details: API 015, Date: Apr 26 2016
   File: /home/game/tf2/tf/addons/sourcemod1/bin/sourcemod_mm_i486.so

   >>> sm_basepath
   "sm_basepath" = "addons/sourcemod1" ( def. "addons/sourcemod" )
    - SourceMod base path (set via command line)
   >>> sm_corecfgfile
   "sm_corecfgfile" = "addons/sourcemod1/configs/core.cfg" ( def. "addons/sourcemod/configs/core.cfg" )
    - SourceMod core configuration file
   >>> sm plugins list
   [SM] Listing 17 plugins:
   01 "Admin Menu" (1.8.0.5907) by AlliedModders LLC
   ...
   17 "Player Commands" (1.8.0.5907) by AlliedModders LLC
   >>> sm exts list
   [SM] Displaying 9 extensions:
   [01] Automatic Updater (1.8.0.5907): Updates SourceMod gamedata files
   ...
   [09] SQLite (1.8.0.5907): SQLite Driver

Ну что же, все пути к каталогам и файлам верны, семнадцать стандартных плагинов и девять расширений запущены. Позже мы разберём кто что делает, а пока продолжим.


Если же при вводе команд выдаются ошибки вида:


   >>> meta list
   Listing 1 plugin:
     [01] 
   >>> sm_basepath
   Unknown command "sm_basepath"

То значит MetaMod не смог загрузить SourceMod. Надо искать в чём дело.


Перечень консольных команд MetaMod: Source — Console Commands (SourceMM)


Расширения находятся в каталоге ~/tf2/tf/addons/sourcemod{1,2}/extensions — для всех поддерживаемых игр на движке Source, не только TF2.


Скомпилированные плагины находятся в каталоге ~/tf2/tf/addons/sourcemod{1,2}/plugins. Если требуется отключить какой-нибудь плагин, то его можно просто перенести в подкаталог disabled. Сами исходные тексты плагинов находятся каталоге ~/tf2/tf/addons/sourcemod{1,2}/scripting. Там же есть компилятор и скрипт compile.sh, который компилирует исходники и помещает скомпилированные плагины в подкаталог compiled, откуда их можно перенести в ~/tf2/tf/addons/sourcemod{1.2}/plugins — и они станут доступны для использования SourceMod.


Теперь можно настроить SourceMod для каждого сервера, редактируя файлы core.cfg в ~/tf2/tf/addons/sourcemod{1,2}/configs. В нашем случае удовлетворимся параметрами по умолчанию. Обратите внимание, что при этом параметром «DisableAutoUpdate No» включено автообновление игровых данных SourceMod в каталогах ~/tf2/tf/addons/sourcemod{1,2}/gamedata.


Так же можно в core.cfg установить параметру «LogMode» значени «game» — тогда логи станут записываться не в отдельные файлы, а в логи игрового сервера. Подробнее в разделе «Логи»


Прочие файлы конфигурации разберём позже.


Однако есть ещё один файл конфигурации — ~/tf2/tf/cfg/sourcemod/sourcemod.cfg — он выполняется при каждой смене карт следом за server.cfg и является общим для всех инсталляций SourceMod. Поэтому оставляем в sourcemod.cfg лишь команды, общие для обоих серверов, а индивидуальные переносим в отдельные файлы для каждого сервера.


   $ dos2unix ~/tf2/tf/cfg/sourcemod/sourcemod.cfg
   $ cp ~/tf2/tf/cfg/sourcemod/sourcemod.cfg ~/cfg/sourcemod_default.cfg
   $ ln -s ~/tf2/tf/cfg/sourcemod ~/cfg/sm

Редактируем ~/tf2/tf/cfg/sourcemod/sourcemod.cfg, традиционно добавляем вывод «echo», исправляем «sm_show_activity» и устанавливаем в «sm_datetime_format» привычный формат вывода даты/времени.


sourcemod.cfg
echo "*** ~/tf2/tf/cfg/sourcemod/sourcemod.cfg"
// SourceMod Configuration File
// This file is automatically executed by SourceMod every mapchange.

// Specifies how admin activity should be relayed to users.
sm_show_activity 28

// Default datetime formatting rules when displaying to clients.
sm_datetime_format "%d %b %Y - %H:%M:%S"

Всё. Остальное удаляем. Позже, когда захочется установить для серверов свои параметры, то можно взять их из сохранённого ~/cfg/sourcemod_default.cfg и указать в каком-либо файле конфигурации сервера. Почему нельзя оставить всё как есть — в оригинальном sourcemod.cfg? А потому что он выполняется при каждой смене карт следом за server{1,2}.cfg — то есть любые установки, например, касающиеся резервных слотов, прописанные в server{1,2}.cfg будут перезаписаны значениями по умолчанию из этого файла. А те же резервные слоты мы будем настраивать для наших серверов по-разному.


При установке новых плагинов SourceMod, их файлы конфигурации могут автоматически создаваться в ~/tf2/tf/cfg/sourcemod/ и становиться общими для обеих инсталляций SourceMod. Собственно, там уже лежит файл с настройками для плагина funcommands.smx

Так же SourceMod по умолчанию периодически скачивает со своего сервера (update.sourcemod.net) обновления файлов с игровыми данными, что найдёт своё отражение в логах. На примере первого сервера (~/tf2/tf/addons/sourcemod1/logs/), строчки 3,4,5:


   L 03/21/2014 - 14:52:41: SourceMod log file session started (file "L20140321.log") (Version "1.5.3")
   L 03/21/2014 - 14:52:41: -------- Mapchange to cp_well --------
   L 03/21/2014 - 14:52:56: [UPDATER] Successfully updated gamedata file "sdkhooks.games/engine.csgo.txt"
   L 03/21/2014 - 14:52:56: [UPDATER] Successfully updated gamedata file "sdktools.games/engine.csgo.txt"
   L 03/21/2014 - 14:52:56: [UPDATER] Successfully updated gamedata file "sm-cstrike.games/game.csgo.txt"
   L 03/21/2014 - 14:52:56: [UPDATER] SourceMod has been updated, please reload it or restart your server.
   L 03/21/2014 - 14:55:26: SourceMod log file session started (file "L20140321.log") (Version "1.5.3")
   L 03/21/2014 - 14:55:26: -------- Mapchange to cp_well --------

Документация по SourceMod — настройка, управление, небольшой FAQ (eng).


Так как очередные обновления Team Fortress 2 могут нарушать хрупкое взаимодействие игры и SourceMod, то имеет смысл отслеживать новые версии и билды и регулярно обновляться.


Продолжение с описанием настройки плагинов и интерактивным взаимодействием игроков с сервером описано в разделе «Плагины SourceMod», а пока двинемся дальше — к настройке запуска серверов.


Автозапуск игровых серверов

Наши игровые сервера уже достаточно взрослые для того, чтобы можно было их включать в автозапуск. Для систем с System V init можно переделать ранее созданные скрипты запуска, либо использовать примеры из TF2 Wiki. Для систем с systemd, как у нас, делаем два файла служб, незатейливо именуемых srcds1.service и srcds2.service.


Справочно, для освежения материала: После всех добавлений (Записи, SourceMod), скрипт запуска того же первого сервера ~/start1.sh в полной комплектации должен был выглядеть как-то так:


start1.sh
#!/bin/sh
#
# Запуск первого сервера.

# Путь к каталогу с игрой, где лежит файл srcds_run
GAMEFOLDER=/home/game/tf2

CMDLINE="+sv_pure 2 -game tf +maxplayers 24 \
    -pidfile ${GAMEFOLDER}/tf/srcds1.pid \
    -ugcpath ${GAMEFOLDER}/steamapps/workshop1 \
    -replay reply1.cfg -replayserverdir server1 \
    +exec autoexec1.cfg +servercfgfile server1.cfg \
    -port 27015 -steamport 26900 +clientport 27005 +tv_port 27020 -strictportbind \
    +sm_basepath addons/sourcemod1 +sm_corecfgfile addons/sourcemod1/configs/core.cfg"

# Запускаем игровой сервер
${GAMEFOLDER}/srcds_run ${CMDLINE}

Мы уже привыкли к скрипту srcds_run, поэтому будем и дальше использовать его. Надо только озаботиться автоматическим перезапуском игрового серевера при выполнении команд quit, _restart. Сейчас скрипт перезапускает сервер только если он закончился аварийно с ненулевым кодом выхода, либо с нулевым при включенном автообновлении.


Вариантов несколько. Первый — можно просто включить стандартное автообновление (секция «Автоматическое» в разделе «Обновление серверов»). Второй — создать копию скрипта с другим именем и поправить его на предмет вечного перезапуска сервера (исходный скрипт править не стоит — он может замениться при обновлении) и использовать его в дальнейшем. Третий — если планируется обновлять сервера самостоятельно, то можно включить автообновление, добавив -autoupdate и два его параметра-сателлита в командную строку запуска, но в качестве скрипта автообновления использовать файлик типа ~/cfg/tf2_quit, состоящий лишь из одной строчки с командой quit, то есть фактически, обновления не будут проверяться.


Знания умножают печали. Но выбор за вами. В нашем же случае, для первого сервера мы в файлах конфигурации ещё не прописывали критерии окончания раундов, а второй сервер вообще будет с нестандартными картами, вполне возможно, что и с бесконечными раундами, поэтому в автозапуске будем использовать третий вариант — с фиктивным автообновлением, а настоящее обновление — через регулярный запуск update.sh скрипта в crontab. Опять же, так интереснее.


Итак, файлы служб. Для удобства мы будем использовать терминальный мультиплексор tmux (желающие могут использовать вместо него screen), который будет запускать скрипт srcds_run. Параметры командной строки перешли практически неизменными из скриптов запуска, которые мы использовали до этого.


Так как игровые сервера запускаются от имени пользователя game, то по-хорошему, можно было бы создавать файлы запуска в пользовательском окружении, в ~/.config/systemd/user/. Но данный раздел документации тестировался на centos 7, где запуск systemd в пользовательском режиме не функционирует, поэтому файлы служб мы создаём от имени root.


Заходим как root, создаём файл /etc/systemd/system/srcds1.service для первого сервера.


srcds1.service
# /etc/systemd/system/srcds1.service
#
# Team Fortress 2 Source Dedicated Server

[Unit]
Description=Team Fortress 2 Source Dedicated Server No 1
After=network.target

[Service]
User=game
Group=game
WorkingDirectory=/home/game/Steam
Type=oneshot
RemainAfterExit=yes
PIDFile=/home/game/tf2/tf/srcds1.pid
ExecStart=/usr/bin/tmux -L socket1 new-session -d '/home/game/tf2/srcds_run \
    +sv_pure 2 -game tf +maxplayers 24 \
    -pidfile /home/game/tf2/tf/srcds1.pid \
    -ugcpath /home/game/tf2/steamapps/workshop1 \
    -replay replay1.cfg -replayserverdir server1 \
    +exec autoexec1.cfg +servercfgfile server1.cfg \
    -port 27015 -steamport 26900 +clientport 27005 +tv_port 27020 -strictportbind \
    -autoupdate -steam_dir /home/game/Steam -steamcmd_script /home/game/cfg/tf2_quit \
    +sm_basepath addons/sourcemod1 +sm_corecfgfile addons/sourcemod1/configs/core.cfg'

ExecStop=/usr/bin/tmux -L socket1 send-keys "quit" Enter ; /usr/bin/sleep 5s ; /usr/bin/tmux -L socket1 kill-session
ExecReload=/usr/bin/tmux -L socket1 send-keys "_restart" Enter
ExecStopPost=/usr/bin/rm -f /home/game/tf2/tf/srcds1.pid

[Install]
WantedBy=multi-user.target

Небольшое пояснение по параметрам ExecStop и ExecReload. Для завершения работы сервера мы посылаем ему лучи любви и команду quit в консоль, ждём несколько секунд для завершения работы, и, пока всё не запустилось заново, прибиваем сессию. Схожий механизм для рестарта сервера. При желании можно добавить вывод предупреждения игрокам о грядущем рестарте — как это было в примере одного из скриптов для обновления, в соответствующем разделе.


Копируем srcds1.service в srcds2.service, корректируем пути, порты и имя tmux сокета. Сохраняем. /etc/systemd/system/srcds2.service:


srcds2.service
# /etc/systemd/system/srcds2.service
#
# Team Fortress 2 Source Dedicated Server

[Unit]
Description=Team Fortress 2 Source Dedicated Server No 2
After=network.target

[Service]
User=game
Group=game
WorkingDirectory=/home/game/Steam
Type=oneshot
RemainAfterExit=yes
PIDFile=/home/game/tf2/tf/srcds2.pid
ExecStart=/usr/bin/tmux -L socket2 new-session -d '/home/game/tf2/srcds_run \
    +sv_pure 2 -game tf +maxplayers 24 \
    -pidfile /home/game/tf2/tf/srcds2.pid \
    -ugcpath /home/game/tf2/steamapps/workshop2 \
    -replay replay2.cfg -replayserverdir server2 \
    +exec autoexec2.cfg +servercfgfile server2.cfg \
    -port 27016 -steamport 26901 +clientport 27006 +tv_port 27021 -strictportbind \
    -autoupdate -steam_dir /home/game/Steam -steamcmd_script /home/game/cfg/tf2_quit \
    +sm_basepath addons/sourcemod2 +sm_corecfgfile addons/sourcemod2/configs/core.cfg'

ExecStop=/usr/bin/tmux -L socket2 send-keys "quit" Enter ; /usr/bin/sleep 5s ; /usr/bin/tmux -L socket2 kill-session
ExecReload=/usr/bin/tmux -L socket2 send-keys "_restart" Enter
ExecStopPost=/usr/bin/rm -f /home/game/tf2/tf/srcds2.pid

[Install]
WantedBy=multi-user.target

Здесь мы специально запускаем две отдельные копии tmux, каждую для своего игрового сервера. Можно было бы, конечно, обойтись сессиями внутри одного tmux (tmux new-session -d -s tf1 и tmux new-session -d -s tf2), но тогда это стало бы дополнительной точкой для epic fail — когда нечаянный Ctrl-C прибил бы все наши сервера.


Не забываем создать файлик ~/cfg/tf2_quit с содержимым:


tf2_quit
quit

Проверяем, что оба .service файла доступны на запись только для root, затем запускаем, включаем:


   # systemctl start srcds1
   # systemctl start srcds2
   # systemctl enable srcds1
   # systemctl enable srcds2

Но сейчас у нас глупая ситуация — игровые сервера стартуют при запуске нашего сервера, но пользователь game не может ими управлять стандартными способами с помощью systemctl (start, stop, reload), хотя вполне может ими «управлять» при помощи Ctrl+C в tmux сессии, с последующим запуском вручную. Исправляем несправедливость.


Пока мы ещё root, запускаем visudo, корректируем файл sudoers, дописав в конец:


sudoers
Defaults:game !requiretty
game    ALL=    NOPASSWD:    /usr/bin/systemctl start srcds1.service, /usr/bin/systemctl start srcds2.service
game    ALL=    NOPASSWD:    /usr/bin/systemctl stop srcds1.service, /usr/bin/systemctl stop srcds2.service
game    ALL=    NOPASSWD:    /usr/bin/systemctl reload srcds1.service, /usr/bin/systemctl reload srcds2.service
game    ALL=    NOPASSWD:    /usr/bin/systemctl status srcds1.service, /usr/bin/systemctl status srcds2.service
game    ALL=    NOPASSWD:    /usr/bin/systemctl enable srcds1.service, /usr/bin/systemctl enable srcds2.service
game    ALL=    NOPASSWD:    /usr/bin/systemctl disable srcds1.service, /usr/bin/systemctl disable srcds2.service

Здесь мы пользователю game предоставляем возможность посредством sudo, без запроса пароля (которого у него и нет) выполнять команды start, stop, reload, status и для кучи enable с disable для первого и второго сервера. А параметр «Defaults: game! requiretty» позволит нам запускать sudo из crontab файла. По желанию можно добавить разрешение на использование иных команд типа is-* и прочих.


Разрешения на команды вида systemctl edit [--full] мы предоставлять не будем. Всё-таки безопасность должна быть… безопасной.


Выходим из-под root, проверяем из-под game:


   $ sudo -l

   Matching Defaults entries for game on this host:
    ...
    ..., !requiretty

   User game may run the following commands on this host:
    (root) NOPASSWD: /usr/bin/systemctl start srcds1.service, (root) /usr/bin/systemctl start srcds2.service
    (root) NOPASSWD: /usr/bin/systemctl stop srcds1.service, (root) /usr/bin/systemctl stop srcds2.service
    (root) NOPASSWD: /usr/bin/systemctl reload srcds1.service, (root) /usr/bin/systemctl reload srcds2.service
    (root) NOPASSWD: /usr/bin/systemctl status srcds1.service, (root) /usr/bin/systemctl status srcds2.service
    (root) NOPASSWD: /usr/bin/systemctl enable srcds1.service, (root) /usr/bin/systemctl enable srcds2.service
    (root) NOPASSWD: /usr/bin/systemctl disable srcds1.service, (root) /usr/bin/systemctl disable srcds2.service

Можно тут же запустить sudo systemctl status srcds1.service, потестировать.


Команды необходимо будет вводить без сокращения параметров. То есть sudo systemctl status srcds1.service — норм, а вот sudo systemctl status srcds1 — уже не торт.


Если при манипуляциях с игровыми серверами с помощью systemctl выдаётся ошибка вида «Failed to stop srcds1.service: Interactive authentication required.» — то значит вы забыли про sudo:-)


Когда всё заработает, можно будет работать с консолями серверов уже как пользователь game, подключаясь через tmux:


   $ tmux -L socket1 attach
   $ tmux -L socket2 attach

Отключаться от tmux сессии — Ctrl+b, d.


Можно прописать алиасы в ~/.bash_profile:


.bash_profile
alias tf1="tmux -L socket1 attach"
alias tf2="tmux -L socket2 attach"

По умолчанию, размер хранимой истории для tmux составляет 2000 строк, но его можно увеличить, например до 10000 строк, создав файл ~/.tmux.conf вида:


.tmux.conf
set-option -g history-limit 10000
set-option -g set-remain-on-exit on

Вторая команда не закрывает tmux сессию при завершении основной программы, что даёт возможность почитать её последнее прости в консоли.


Перемещаться по истории — Ctrl+b, [, а далее обычными клавишами — Up/Down, PgUp/PgDown. Выход из этого режима — q.


Логи

Логов у нас будет много и разных. А именно:


Логи cron


У нас через cron запускаются скрипты проверки обновлений, сервера статистики HLstatsX, удаления устаревших Записей, а их вывод отражается в /var/log/cron в виде


   Jun 15 12:55:01 server CROND[584]: (game) CMD (cd $HOME/stat/scripts && ./run_hlstats start 2 27500 1)
   Jun 15 12:55:01 server CROND[583]: (game) CMDOUT (HLstatsX:CE daemon control)
   Jun 15 12:55:01 server CROND[583]: (game) CMDOUT (http://www.hlxce.com)
   Jun 15 12:55:01 server CROND[583]: (game) CMDOUT (---------------------------)
   Jun 15 12:55:01 server CROND[583]: (game) CMDOUT (Daemon is already running on port 27500)
   Jun 15 12:55:01 server CROND[583]: (game) CMDOUT (Daemon is already running on port 27501)
   ...

Так как /var/log/cron никто прочитать не сможет кроме пользователя root, а ему это не интересно, то хотелось бы дать доступ пользователю game к логам его crontab файла. Конечно, можно для каждого скрипта в конце строки запуска дописать что-то вида »… >> $HOME/log/cron.log», но эти «логи» будут без меток времени, да и вообще, кустарщина. Более культурный варинт »… | /usr/bin/logger --tag srcds» ничего не меняет — ну будут сообщения попадать не в /var/log/cron, а в /var/log/messages, разницы-то.


Поэтому мы в настройках демона rsyslog сделаем так, что сообщения от cron, начинающиеся с имени пользователя »(game)», перенаправляются в файл /home/game/log/cron.log, владельцем которого является пользователь game, и не попадают в дальнейшую обработку (в «cron.* /var/log/cron»)


У нас установлен rsyslogd версии 8.19, и в /etc/rsyslog.conf директива $IncludeConfig /etc/rsyslog.d/*.conf находится до правила cron.* /var/log/cron, поэтому перехватывать сообщения от наших скриптов будем, создав как пользователь root файл /etc/rsyslog.d/srcds-10-cron.conf с содержимым:


srcds-10-cron.conf
# /etc/rsyslog.d/srcds-10-cron.conf

if $syslogfacility-text == "cron" and $msg startswith " (game)" then {
  action(
    type = "omfile"
    fileOwner = "game"
    fileGroup = "game"
    file = "/home/game/log/cron.log"
  )
  stop
}

Подробнее об использованных командах — omfile, properties.


Проверяем синтаксис, если всё ok, то перезапускаем демона и наслаждаемся свежесозданным логом.


   # rsyslogd -N 2
   # systemctl restart rsyslog

Пока мы ещё root, настраиваем ротацию логов, создав файл /etc/logrotate.d/srcds-cron:


srcds-cron
#/etc/logrotate.d/srcds-cron

/home/game/log/cron.log {
    su game game
    daily
    dateext
    maxage 3
    missingok
    copytruncate
    compress
    notifempty
}

Проверяем:


   # logrotate --debug --force /etc/logrotate.d/srcds-cron

Логи клиента Steam


Эти логи находятся в ~/Steam/logs/. Ротация происходит автоматически, силами самого клиента. Текущий лог переименовывается в *.previous.txt, предыдущий *.previous.txt предварительно удаляется. Специально мы их обрабатывать не будем, какой-то необходимости хранить исторические данные нет — текущие проблемы с подключением можно посмотреть в активных логах, а что было с коннектом к серверам Valve девятого термидора второго года — nobody cares…


Логи веб-сервера


У нас с игровыми серверами сотрудничают три виртуальных веб-сервера, обслуживающих:


  • Fast Download, логи в /var/www/fastdl.example.org/log/
  • Записи (Replay), логи в /var/www/replay.example.org/log/
  • HLstatsX, логи в /var/www/stat.example.org/log/

Ротация этих логов у нас настроена в /etc/logrotate.d/srcds-nginx, предполагается, что логи с ошибками будем просматривать самостоятельно, а логи доступа можно скармливать хоть Elasticsearch, Kibana и Logstash —, но описание ELK стека вне объёма данного проекта.


Логи SourceMod


Эти логи находятся в каталогах ~/tf2/tf/addons/sourcemod{1,2}/logs. Их настройки прописаны в соответствующих ~/tf2/tf/addons/sourcemod{1,2}/configs/core.cfg:


Logging on
логи вкл/выкл


LogMode daily
ротация логов — ежедневная (daily), при смене карты (map), и третий режим (game) — писать в логи игрового сервера.


DebugSpew no
детализировать ли в логах обновление игровых данных


Логи SourceMod ведутся довольно скромно, поэтому достаточно настроить их регулярную ротацию, либо не заморачиваться и просто в core.cfg прописать LogMode game, тогда они гармонично впишутся в логи игровых серверов, как-то так:


   L 06/20/2016 - 04:53:24: Executing dedicated server config file server1.cfg
   L 06/20/2016 - 04:53:32: server_cvar: "sv_tags" "HLstatsX:CE,cp,increased_maxplayers"
   L 06/20/2016 - 04:53:32: tf_server_identity_account_id not set; not logging into registered account
   L 06/20/2016 - 04:53:32: server_cvar: "sv_contact" "game@example.org"
   L 06/20/2016 - 04:53:33: [UPDATER] Successfully updated gamedata file "core.games/common.games.txt"
   L 06/20/2016 - 04:53:33: [UPDATER] Successfully updated gamedata file "sdkhooks.games/game.doi.txt"
   L 06/20/2016 - 04:53:33: [UPDATER] Successfully updated gamedata file "sdkhooks.games/master.games.txt"
   L 06/20/2016 - 04:53:33: [UPDATER] Successfully updated gamedata file "sdktools.games/game.doi.txt"
   L 06/20/2016 - 04:53:33: [UPDATER] Successfully updated gamedata file "sdktools.games/master.games.txt"
   L 06/20/2016 - 04:53:33: [UPDATER] SourceMod has been updated, please reload it or restart your server.
   L 06/20/2016 - 04:53:33: Connection to Steam servers successful.
   L 06/20/2016 - 04:53:33:    Public IP is 192.0.2.0.
   L 06/20/2016 - 04:53:33: Assigned anonymous gameserver Steam ID [A:1:123456789:1234].
   L 06/20/2016 - 04:53:33: VAC secure mode is activated.
   L 06/20/2016 - 04:53:34: server_cvar: "sm_nextmap" "cp_well"

Логи игровых серверов


Логи игровых серверов находятся в ~/log/server{1,2}/. Минимальную настройку логов мы провели при написании конфигурационных файлов, но сейчас рассмотрим значение некоторых переменных поподробнее:


log off
глобально включает (on) ведение логов данного игрового сервера. Логи могут направляться в файл (sv_logfile), на консоль сервера (sv_logecho), транслироваться по UDP (logaddress_add).


sv_logfile 1
включает запись логов в файл (1), при этом требуется включение предыдущей командой. Каталог для логов и формат имени файлов задаются параметрами sv_logsdir и sv_logfilename_format.


sv_logsdir logs
каталог для логов. По умолчанию, был бы ~/tf2/tf/logs, но так как мы логи наших двух серверов разводим по разным каталогам, то каталоги /home/game/log/server1 и /home/game/log/server2 соответственно. Хотя, конечно, можно было бы писать в один каталог, но с разным именованием файлов.


sv_log_onefile 0
вести запись логов в один файл (1), либо при смене карты создавать новый (0), например: l0618000.log → l0618001.log → l0618002.log… l0619000.log → l0619001.log и так далее. Отметим две особенности при включении данного параметра: во-первых, при наступлении новых суток автоматической ротации лога l0618000.log → l0619000.log не произойдёт; во-вторых, если в консоли или посредством какого-нибудь файла конфигурации сделать log off и тут же log on, то запись начнётся в новый файл, с инкрементированным номером.


sv_logfilecompress 0
при начале записи в новый файл логов, старый будет сжиматься gzip и переименовываться в .log.gz. Задумка хорошая, реализация под Linux хромает. Так, при установке этой переменной в »1» и смене карты (что, при нашем sv_log_onefile = »0» вызывает запись логов в новый файл), на консоли сервера появляется ругань, создаётся пустой .gz файл, а старый файл лога не удаляется. А всё из-за регистра первого символа в имени файла — «l» vs «L», что принципиально в Linux:


      ---- Host_Changelevel ----
      Compressing /home/game/log/server2/L0713019.log to /home/game/log/server2/L0713019000.log.gz...
      Success. Removing /home/game/log/server2/L0713019.log.
      Unable to remove /home/game/log/server2/L0713019.log!
      Unable to remove /home/game/log/server2/L0713019.log!
      Server logging data to file /home/game/log/server2/L0713020.log
      Applying new item schema, version 5C0BC93D

sv_logfilename_format ""
формат именования файлов с логами. Должен указываться до параметра «log on». Если специально не указывать, то файлы с логами сейчас именуются как l<Месяц><День><000...999>.log, где первый символ — , а последнее трёхзначное число в имени инкрементируется для каждого нового лога, в пределах текущих суток. Можно задавать свой формат, с использованием параметров функции strftime. То есть, при указании «sv_logfilename_format %Y%m%d» файл будет выглядеть как »20160714.log». Автонумерация в этом случае будет вида 20160714.log → 20160714_000.log → 20160714_001.log и так далее. Забавно, но при явной установке формата, начинает работать параметр sv_logfilecompress — предыдущий файл лога действительно архивируется.


log_verbose_enable 0
включает (1) подробное ведение логов. Так, каждые несколько секунд (определяется log_verbose_interval) туда пишутся текущие трёхмерные координаты игроков. Лог разрастается неимоверно. Включать, пожалуй, имеет смысл разве что для построения тепловых карт движения игроков, с селекцией по классам. Что-то вроде http://geit.uk/blog/player-flow/.


log_verbose_interval 3.0
задаёт интервал записи подробного лога. По умолчанию — каждые 3 секунды.


sv_rcon_log 1
при отключении (0), не выводит на консоль и в лог поступающие rcon команды (результаты выполнения команд выводятся на консоль по-прежнему). Так как в нашей конфигурации rcon использует исключительно HLstatsX, то экономия в части размеров логов невелика, но всё же спама на консоли сервера будет поменьше, без вот этого:


      rcon from "192.0.2.0:60533": command "status"
      rcon from "192.0.2.0:60533": command "hlx_sm_psay "30" 2 "Round Over - All actions/frags are ignored <...>
      rcon from "192.0.2.0:60533": command "hlx_sm_psay "30" 1 "Red got 10 points for Round Win""
      rcon from "192.0.2.0:60533": command "hlx_sm_psay "30" 1 "Asuka got 2 points (1,025) for Kill Assist""

sv_logecho 1
дублирует вывод лога на консоль. Естественно, отключаем (0).


sv_logbans 0
при включении (1) фиксирует информацию о бане игрока в лог:


      L 07/13/2016 - 23:09:31: Addip: "<><><>" was banned by IP "for 10.00 minutes" by "Console" (IP "10.1.1.1")

logaddress_add
указывает адрес: порт, куда будут транслироваться логи по UDP, в нашем случае для HLstatsX. Первому серверу мы устанавливали 192.0.2.0:27500, второму — 192.0.2.0:27501


logaddress_del
удаляет адрес: порт из трансляции.


logaddress_delall
удаляет все адреса из трансляции


developer 0
обеспечивает более подробный вывод информации на консоль сервера. Варианты значений — 0 (по умолчанию), 1, 2, 3…


Вот логи игровых серверов имеет смысл хранить некоторое время, на случай жалоб на того или иного игрока — мат в чате, оскорбление других игроков, двести восемьдесят вторая статья и так далее.


Обычно логи сервера при полной загрузке и интенсивной движухе растут на 3.5 — 4.0 тыс. строк в час, около 0.5 — 0.6 Мб.


Запись логов ведётся таким способом, что если запускать ротацию логов с помощью logrotate без параметра copytruncate в файле настроек, то после неё новых логов мы не увидим до смены карты (и то, при sv_log_onefile 0). Запуск же с параметром copytruncate приводит к обрезанию старого лога посередине строки (как повезёт — так как игровые сервера, если не включать sv_logflush 1, буферизируют запись логов в файл, причём не построчно, а покилобайтно) и бинарному мусору в начале нового файла, да так, что при уже его ротации, архив состоит из одной строки «Binary file (standard input) matches».


Учитывая это, работу с логами серверов можно организовать следующими способами:


  1. Очень ленивый. Ничего не делать. При наличии инстинкта самосохранения всё же обеспечить удаление старых логов. Ну или просто отключить их ведение.


  2. Ленивый. Для обеспечения ротации логов и экономии места на диске включить комбинацию «sv_log_onefile 0», «sv_logfilecompress 1» и «sv_logfilename_format %Y%m%d», добавить в crontab find $HOME/log/server{1,2}/*.gz -type f -mtime +30 -delete, что позволит обойтись без logrotate. Хороший, годный вариант. В случае «бесконечных» карт можно либо уповать на время от времени случающиеся обновления серверов, при перезапуске гарантирующие ротацию логов, либо принудительно перезапускать карту хотя бы раз в неделю — добавить в crontab запуск команды tmux -L socket1 send-keys "changelevel_next" Enter в часы минимальной загрузки.


  3. Активный. Если есть необходимость мониторить логи в реальном режиме времени, как-то их анализировать и обрабатывать, то можно использовать возможности rsyslog демона. Плагин omfile — File Output Module мы использовали выше в обработке лога cron, а здесь можно использовать imfile — Text File Input Module.

Попробуем настроить сохранение чата игроков первого сервера в отдельном логе. Cоздаём (из-под root) файл /etc/rsyslog.d/srcds-20-chat.conf:


srcds-20-chat.conf
#/etc/rsyslog.d/srcds-20-chat.conf

module(load="imfile" mode="inotify")

# Мониторит все *.log файлы первого сервера
input(
  type = "imfile"
  tag = "srcds1:"
  file = "/home/game/log/server1/*.log"
  ruleset = "chat-1"
)

# $now - текущая дата, на момент записи лога, в формате YYYY-MM-DD
template (name="chat1-log-name" type="string" string="/home/game/log/chat1-%$now%.log")

# Мониторим общий чат (say), командный (say_team), и steam id и ip игроков, входящих на сервер
ruleset(name = "chat-1") {
  if ($msg contains [" say ", " say_team ", " connected, address "]) then {
    action(
      type = "omfile"
      fileOwner = "game"
      fileGroup = "game"
      dynafile = "chat1-log-name"
    )
  }
}

При таких настройках, лог чата будет выглядеть как:


   Jul 23 10:40:46 server srcds1: L 07/23/2016 - 10:40:27: "Asuka<3><[U:1:12345678]>" say_team "Valar morghūlis"
   Jul 23 10:40:46 server srcds1: L 07/23/2016 - 10:40:34: "Rei<4><[U:1:09876543]>" say "Valar dohaeris, desu"

Тут стандартный формат rsyslogd — дата+время, имя сервера (железного), метка из input, и сама строчка лога. Отличие в секундах вызвано буферизацией записи игровых логов. При желании можно настроить дополнительную проверку текста сообщений на ключевые слова с немедленным извещением нас по электронной почте (модуль ommail в rsyslog)


Проверяем синтаксис, если всё ok, то перезапускаем демона.


   # rsyslogd -N 2
   # systemctl restart rsyslog

Если новый лог будете создавать в том же каталоге, откуда считываются исходные (параметр file в секции input), то убедитесь, что его имя не попадает под маску файлов из секции input, которые мониторит rsyslogd. А то получится очень, гм… рекурсивненько.

Возвращаясь к настройкам ведения логов, стоит отметить ещё два параметра:


con_logfile ""
создаёт отдельный файл лога, куда записывается всё что выводится на консоль сервера. Имя файла указывается либо с полным путём, либо относительно каталога ~/tf2/tf/ (не logs!). Если не указывать расширение ».log», то оно добавится автоматически. Имеет аналогичный параметр командной строки -consolelog  — только там лучше уже указывать расширение, а то получится забавно — при старте сервера создадутся три консольных лога: сначала запись пойдёт в ~/tf2/bin/, затем в ~/tf2/tf/, ну и наконец в ~/tf2/tf/.log.


con_timestamp 0
при включении (1), весь вывод в консольный лог будет предваряться метками времени, как в примере ниже. Если указывать в командной строке запуска вместе с предыдущим параметром, как +con_timestamp 1 -consolelog , то метки начнутся лишь с третьего файла.


В отличие от серверных логов, запись в консольный лог идёт построчно, и вполне корректно работает logrotate с параметром copytruncate.


Следует быть осторожным с включением записи этого, безусловно полезного лога. Иногда на игровой сервер что-то находит, и он начинает мощно спамить в консоль сообщениями вида:


   07/15/2016 - 14:59:59: DataTable warning: player: Out-of-range value (72483.898438/65536.000000) in SendPropFloat 'm_flLastDamageTime', clamping.
   07/15/2016 - 14:59:59: DataTable warning: player: Out-of-range value (72492.539062/65536.000000) in SendPropFloat 'm_flLastDamageTime', clamping.
   07/15/2016 - 14:59:59: DataTable warning: player: Out-of-range value (72479.742188/65536.000000) in SendPropFloat 'm_flLastDamageTime', clamping.

При скорости потока, как правило ~ 125 сообщений в секунду, такими темпами файл с консольным логом прирастает на ~ 64 Мб в час, 1.5 Гб в сутки, что особенно неприятно в случае «долгоиграющих» карт, так как при смене карты эта ошибка обычно исчезает. Лечится либо прописыванием критериев, обеспечивающих регулярную ротацию карты, либо смирением с полуторагигабайтным логом в сутки, тем более, что сжимаются они неплохо, а хранить их можно недолго.


Настраиваем ротацию и этих логов, создав из-под root файл /etc/logrotate.d/srcds-server:


srcds-server
#/etc/logrotate.d/srcds-server

/home/game/log/console*.log
{
    su game game
    daily
    dateext
    rotate 7
    copytruncate
    notifempty
    missingok
    compress
#    compresscmd /home/game/logrotate-filter.sh
}

Проверяем логику работы:


   # logrotate --debug --force /etc/logrotate.d/srcds-server

Можно туда добавить костыль — строчку «compresscmd /home/game/logrotate-filter.sh», а сам ~/logrotate-filter.sh вида:


logrotate-filter.sh
#!/bin/sh

/bin/grep -v ": DataTable warning:" | /bin/gzip -6 

то есть в качестве архиватора будет вызываться не gzip, а этот скрипт, который входной поток пропустит через grep, вырезая DataTable warning, а остальное передавая gzip, as planned.


Кстати, как вариант — можно включить дублирование лога в консоль (sv_logecho 1), выключить ведение основного лога (sv_logfile 0) и активировать

© Habrahabr.ru