GitHub вместо Steam Workshop для Garry's Mod
InGameShop (IGS) - внутриигровая менюшка для Garry's Mod серверов, через которую игроки донатят их владельцам с помощью gm-donate.net. До недавней поры установка происходила через Steam Workshop. Сейчас готова версия, которая 1 запросом скачивает и выполняет код. Внизу будет реализация
Разработка такой версии изначально казалась задачкой на вечерок под кофеек, но много раз откладывалась из-за каких-то нюансов. Было много идей, как все реализовать, но выжила только одна, о которой пост
Что получилось
Open Source репозиторий на GitHub, где создание нового тега приводит к автоматической сборке билда IGS, который можно скачать и установить тупым простым HTTP GET запросом
Преимущества
Вкратце: проще для пользователей и разработчиков
- Пользователям не нужно учиться, как создавать коллекцию в Steam Workshop, добавить в нее ваш/наш аддон, а потом еще привязать к серверу. Для установки достаточно 1 файла с http.Fetch + RunString
- Разработчикам с базовым знанием Git проще релизить аддон, потому что упаковать его в .gma, затем загружать в Workshop после малейших фиксов это геморрой. Разработчики на Linux/Mac без установленного Steam и вовсе не смогут залить обновление в Workshop, зато с этим способом это простой
git tag NAME -am "Hello world!"
+git push origin NAME
- Когда Workshop лежит или на аддон кто-то пожаловался или вдруг Steam изменил API (а мы сталкивались со всем из перечисленного), то страдают все. Аддон становится недоступным и отключается на всех серверах
- Пользователи могут сделать форк репозитория, делать свои модификации IGS и использовать свои версии вместо нашего репозитория. Код открытый, любой может его изменить или убедиться в безопасности
- Возможность поддержки сразу нескольких версий скрипта без необходимости выкладывать новый аддон в воркшоп, когда происходят изменения без обратной совместимости. Каждый обновляется как хочет и когда хочет
Недостатки
Вкратце: для работы нужен интернет и отсутствие параноидальных античитов
- Без интернета аддон не загрузится. В нашем случае это не имеет значения, но для оффлайн аддонов это может быть важно
- Если на серверах есть блокираторы RunString/CompileString, то код не запустится
- Кастомные модельки придется загружать сторонним аддоном. В веб скрипт их засунуть не получится
- Если у вас используются .dll, то придется искать им альтернативу. Для замены пушей через socket.dll мы сделали long polling microservice и только выиграли от этого.
- Некоторые хостинги могут блокировать запросы к GitHub, но это скорее проблема подвальных серверов, крупные хостеры этим не страдают
В чем сложность?
Нельзя просто так взять и залить какой-нить zip в интернет, скачать его с гмода, распаковать там и запустить, как ни в чем не бывало. Ну вообще-то кое-что подобное сделать можно, но не суть. Не льзя короче.
Исходный код содержится во множестве файлов, включая отдельные энтити, требующие особых правил загрузки и сторонние модули, которых нет в репозитории, но которые все равно должны подгружаться скачанным кодом.
Сама web загрузка тоже с приколдесом и срабатывает после ПОЛНОЙ загрузки сервера и всех загрузочных хуков, вроде InitPostEntity, PostGamemodeLoaded, GM:Initialize и т.д., а значит, что если в коде будут использоваться эти хуки, то они сами по себе уже не будут вызваны.
Кроме того нужно было сделать, чтобы скрипт умел автоматически обновляться до свежей версии и сам понимать, какую версию ему можно установить, а какую нет. Мы же не хотим, чтобы наша любимая Windows XP превратилась в Windows 10 без нашего желания, правда? Ну вот и некоторые клиенты не очень любят обновляться.
Если этого мало, то учитывайте еще то, что путь в include может быть как относительный (init.lua), так и абсолютный (igs/interface/vgui/init.lua), а код должен понимать, где искать файл.
Теперь можно выдохнуть? Нет. Пора думать про безопасность и про то, чтобы клиенты не могли изменить какой-то файл и добавить в него клиентский скрипт (например, чит). Вообще-то, мы тупо решили не кешировать файлы, но если захотите сделать такую глупость вместо нас, то сравнивайте хотя бы util.CRC файлов на клиенте и сервере.
Реализация
Было несколько идей реализации, но все они оказались говном. Эта, конечно, тоже пахнет, но не так, как другие. Честно
Вкратце: аддон упаковывается в много строк в 1 superfile.txt в формате
путь код
, гмод скачивает его, а специальный include(путь) выполняет RunString, если путь есть в файле
Упаковщик
Нужно написать программку (вот), которая упакует ваш аддон в 1 файл. В нашем случае это superfile.txt. На момент написания поста это происходит так: скрипт рекурсивно "бегает" по папке addons/igs-core
, читает каждый файл, превращает его в одну строку, а затем добавляет в superfile.txt путь к этому файлу и его содержимое.
Частичка superfile.txt. Много строк типа igs/init.lua print("я очечко")
Автоматизатор + Доставщик
GitHub будет сам делать superfile.txt и создавать с ним релиз каждый раз, когда будет создаваться новый git tag
.
Вообще, GitHub это еще и хостинг для нашего аддона и история изменений и API для поиска и скачивания нужной версии кода и хипстерские иконк и в readme и вычислительные мощности для каких угодно автоматизаций и совместная разработка и вообще GitHub это чудо света. Даже сын маминой подруги не так крут. Всем советую
Для удобства GitHub еще создаст igs-mod.zip (архив этой папки), который владельцы серверов должны установить себе на сервер и тоже добавит его к релизу. Это позволяет легко скачать настройщик, не скачивая весь репозиторий
Получалка
Это простой http.Fetch, который скачивает и запускает главный файл. Он встроен в igsmodification (Настройщик), который пользователям так или иначе все равно придется установить на сервер, поэтому Получалку я встроил именно туда
В самом упрощенном виде выглядит вот так:
http.Fetch("https://raw.githubusercontent.com/GM-DONATE/IGS/main/addons/igs-core/lua/autorun/l_ingameshop.lua", RunString, error)
Загрузчик
Встречайте вишенку на торте. Именно ссылка на Загрузчик указа на в Получалке и именно Загрузчик ищет, скачивает superfile.txt и занимается тем, чтобы все исправно загружалось (энтити, модули, хуки).
Загрузчик находит и скачивает superfile.txt, парсит его и засовывает в таблицу IGS.CODEMOUNT
.
Именно Загрузчик говорит клиентам, какой файл нужно скачивать, поэтому не будет такого, что клиенты скачают одну версию, а сервер другую
В Загрузчике есть 3 основных функции: IGS.sh
, IGS.sv
и IGS.cl
. По сути это обертки для include()
. Эти функции вызываются примерно так: IGS.sv("igs/core_sv.lua")
а функция потом ищет указанный путь в IGS.CODEMOUNT
(кеш superfile.txt) и если такой путь есть, то запускает код с него, а если нет, то ищет файл в аддонах и грузит оттуда обычным include. Это позволяет запускать модули, которые уже есть на сервере
Бонус
Можно не читать, если не собираетесь делать свою реализацию веб загрузки
- git action для создания gma. Сначала планировалось просто делать .gma из аддона, затем скачивать именно .gma с GitHub, потом распаковывать на сервере вместо superfile.txt, но этот способ показался мне избыточным
- gma unpacker. Именно этот скрипт должен был распаковывать скачанный .gma. Автор IVogel.
- Если все же хотите использовать кастом модельки в скриптах, то добавляйте их в свой аддон-Получалку, заливайте в воркшоп и комбинируйте с
resource.AddWorkshop
- Нет смысла скачивать superfile.txt, кешировать его и потом грузиться с него вместо скачивания при каждой загрузке сервера или клиента. Это уменьшает защиту от читеров и усложняет вам поддержание такого решения. Кода получится намного больше, а выгоды от этого нет
- Был вариант с двумя
SH+CL
иSH+SV
файлами, чтобы "сэкономить трафик", но это экономия на копейках, потеря в рублях. Тяжело будет отделять и поддерживать сразу 2 файла, а выгода будет в паре килобайт, что в современном мире – ничто - Одна из идей реализации - упаковка кода в файл построчно таким образом, чтобы код выполнялся один за другим и нормально работал. Но тут и проблема "как упаковать" и проблема "что делать с энтити и сторонними модулями"
- Еще одна из идей реализации это через реплейс инклюдов в коде сразу содержимым из файлов, но не все файлы всегда находились бы на сервере. Отсутствовала бы возможность работы с модулями и само решение неоправданно более сложное
- Идею можно совершенствовать, сделать полноценный фреймворк для упаковки и доставки аддонов через GitHub. Можно сделать, чтобы superfile.txt был в json формате и тогда не будет проблем с определением ошибки в коде. Можно прикрутить поддержку загрузки тулов и даже при сильном желании кастом моделек