RAGE:MP: скриптовые ошибки при запуске gamemode
Запустил ./ragemp-server, а вместо привычной строки про старт на порту консоль сыплет красным Node.js-трейсом — знакомая ситуация, если гейммод собран из нескольких пакетов или недавно обновлялся. В отличие от FiveM с его Lua-трейсами, RAGE:MP работает на чистом Node.js, и ошибки здесь — это ровно то, что печатает сам движок JavaScript: стек вызовов, путь до файла, номер строки. Разберём, как читать этот вывод, быстро находить виновный package в packages/ и чинить три самые частые причины падения при старте: забытые зависимости, порядок загрузки и рассинхрон версий API.
Содержание
- Как выглядит ошибка Node.js в консоли RAGE:MP
- "Cannot find module": забытый npm install
- Порядок загрузки пакетов: чего conf.json на самом деле не умеет
- Конфликт версий API: гейммод писался под другую сборку RAGE:MP
- Нативные npm-модули: почему bcrypt или sqlite3 роняют сервер, а mysql2 — нет
- Как локализовать проблемный package методом исключения
Как выглядит ошибка Node.js в консоли RAGE:MP
Сервер грузит гейммод так: сканирует все подпапки в packages/ и для каждой, где есть index.js, выполняет require() этого файла — точно так же, как обычный Node.js require любого модуля. Если внутри index.js (или любого файла, который он подключает через require) есть ошибка, вы увидите стандартный вывод Node, а не какое-то специальное сообщение от RAGE:MP.
Самый частый случай — отсутствующая зависимость:
/home/ragemp/ragemp-srv/packages/myrp/index.js:12
const db = require('mysql2');
^
Error: Cannot find module 'mysql2'
Require stack:
- /home/ragemp/ragemp-srv/packages/myrp/index.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
at Module._load (node:internal/modules/cjs/loader:920:27)
at Module.require (node:internal/modules/cjs/loader:1141:19)
Синтаксическая ошибка выглядит иначе, но так же однозначно указывает файл и строку:
/home/ragemp/ragemp-srv/packages/myrp/handlers/vehicles.js:47
if (player.vehicle) {
^
SyntaxError: Unexpected end of input
at wrapSafe (node:internal/modules/cjs/loader:1153:18)
Главное правило чтения такого трейса — верхняя строка с путём к файлу почти всегда и есть источник проблемы, а не что-то ниже по стеку. Путь абсолютный или относительный от рабочей директории сервера, так что сразу понятно, в каком package искать. Если гейммод собирается через babel/webpack и в packages/ лежит уже минифицированный бандл — путь и строка будут указывать в собранный файл, а не в исходник, и придётся смотреть карту сборки (sourcemap) или пересобирать с читаемым выводом, чтобы найти реальное место.
Если ошибка не при старте, а во время игры (после конкретного действия игрока) — трейс точно так же укажет файл и строку обработчика события, который упал. Общие принципы поиска причины по логам и таймингу разобраны в статье про логи и краш-репорты — они применимы и к RAGE:MP, не только к чисто игровым крашам.
"Cannot find module": забытый npm install
Самая массовая причина падения гейммода на старте — RAGE:MP сам по себе не устанавливает npm-зависимости. Если вы скачали гейммод с GitHub и просто скопировали папку в packages/, но не выполнили npm install внутри неё, при первом же require('express') или require('mysql2') сервер упадёт с MODULE_NOT_FOUND ещё до старта.
cd ~/ragemp-srv/packages/myrp
npm install
Node ищет модули не только в node_modules самого package — правило резолвинга работает так: сначала packages/myrp/node_modules, если там нет — поднимается на уровень выше, до packages/node_modules, а если и там пусто — до корня сервера. Это можно использовать: если несколько ваших пакетов используют одни и те же зависимости (например, и серверная логика, и античит-модуль тянут mysql2), удобнее держать один общий node_modules в корне сервера рядом с ragemp-server, чем дублировать npm install в каждой подпапке.
Ещё одна деталь: package.json внутри package — это просто манифест npm-зависимостей, RAGE:MP его не читает и никаких специальных полей вроде main или dependencies для собственных нужд не использует. Точка входа всегда жёстко index.js в корне подпапки package — если файл называется иначе (server.js, main.js), RAGE:MP его просто не найдёт и промолчит, не выдав никакой ошибки вообще.
Поднять сервер RAGE:MP за пару минут
Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.
Создать серверПорядок загрузки пакетов: чего conf.json на самом деле не умеет
Тут стоит сразу закрыть частое заблуждение, особенно у тех, кто пришёл из FiveM. В server.cfg FiveM порядок ресурсов задаётся явно — директивами ensure/start построчно сверху вниз. В conf.json RAGE:MP такого списка нет вообще, ни одного поля — про пакеты там ни слова, весь конфиг посвящён порту, лимитам и сетевым параметрам (подробный разбор всех полей — в статье про установку сервера RAGE:MP).
Порядок, в котором сервер требует index.js из подпапок packages/, определяется порядком чтения директории файловой системой — на большинстве Linux-ФС (ext4, XFS) это на практике почти всегда алфавитный порядок, но формально POSIX это не гарантирует. Если ваш гейммод разбит на несколько независимых пакетов и один ожидает, что переменные или подключение к БД из другого уже готовы к моменту его собственного require, — это хрупкая конструкция, которая может однажды перестать работать без единой правки кода, просто из-за смены файловой системы или её дефрагментации.
Два рабочих варианта, чтобы не зависеть от порядка сканирования директории:
- Форсировать порядок именем папки. Если пакеты правда должны грузиться в конкретной последовательности, называйте их с числовым префиксом —
00-core,10-database,20-gamemode— так порядок предсказуем даже теоретически, а не только "почти всегда":
packages/
00-core/
index.js
10-database/
index.js
20-gamemode/
index.js
- Отвязаться от порядка через собственное событие. Пакет, который инициализирует что-то общее (подключение к БД, глобальный конфиг), после готовности вызывает своё событие через
mp.events.call, а зависимые пакеты не полагаются на момент своей загрузки, а подписываются и ждут:
// в 00-core/index.js
global.db = require('mysql2').createPool({ /* ... */ });
mp.events.call('coreReady');
// в 20-gamemode/index.js
mp.events.add('coreReady', () => {
// тут global.db уже точно есть
});
Второй вариант надёжнее первого и не ломается даже если вы позже перенесёте логику в один package — но на практике для несложных RP-гейммодов проще всего вообще не разносить логику по нескольким независимым packages/, а держать один package с чётко упорядоченными require() внутри собственного index.js: тогда порядок загрузки контролируете вы сами, построчно, как в обычном Node-приложении.
Конфликт версий API: гейммод писался под другую сборку RAGE:MP
Ошибка на старте не всегда про модули — иногда index.js благополучно грузится, но падает при первом обращении к объекту mp, потому что метод, на который рассчитывал автор гейммода, в текущей версии сервера называется иначе или ведёт себя по-другому:
TypeError: mp.Colshapes.newSphere is not a function
at /home/ragemp/ragemp-srv/packages/myrp/world/zones.js:8:15
RAGE:MP развивается, и часть API между релизами меняется — переименовываются методы, немного иначе работают события стриминга, добавляются новые сущности. Если вы взяли гейммод, написанный несколько лет назад под старую сборку сервера, а поставили последнюю версию с сайта rage.mp, часть вызовов вполне может не совпасть. Признаки именно этой проблемы:
- Ошибка не
Cannot find module, аis not a functionилиCannot read properties of undefined— то есть объектmp.ЧтоТосуществует, но у него нет ожидаемого метода/свойства. - Ошибка появляется не в первой строке
index.js, а глубже, в конкретном обработчике — то есть базовая загрузка прошла нормально. - В репозитории гейммода на GitHub давно не было коммитов, а issues содержат жалобы на несовместимость с новыми версиями сервера.
Что делать: смотреть README и package.json гейммода на предмет упоминания версии RAGE:MP, под которую он писался, и сверять с тем, что реально скачали (сама версия сервера обычно печатается одной из первых строк в консоли при старте — стоит проверить это в первую очередь). Если явного указания версии нет — искать конкретный сломанный метод в актуальной документации API на сайте RAGE:MP и смотреть, не переименован ли он или не изменилась ли сигнатура. Патчить обычно приходится точечно — заменить конкретный вызов на актуальный аналог, а не переписывать гейммод целиком.
Если сомневаетесь, стоит ли вообще связываться с гейммодом на чужом, местами устаревшем API, или проще писать логику с нуля — сравнение архитектуры RAGE:MP с более распространённым FiveM разобрано в статье чем установка RAGE:MP отличается от FiveM.
Нативные npm-модули: почему bcrypt или sqlite3 роняют сервер, а mysql2 — нет
Отдельная категория падений — не про сам гейммод, а про то, как некоторые npm-пакеты устроены изнутри. Часть модулей (классический bcrypt, sqlite3, некоторые версии нативных драйверов) содержат скомпилированный C++-код, который собирается под конкретную версию и ABI движка V8/Node в момент npm install. RAGE:MP запускает гейммод внутри своего встроенного Node.js-рантайма — и если он не совпадает по версии с тем Node, под которым модуль был скомпилирован на вашей машине или в CI, при require вылезет что-то в духе:
Error: The module was compiled against a different Node.js version
или сервер падает совсем без явного сообщения об ABI, просто вылетая при первом обращении к нативному модулю. Надёжного способа гарантированно пересобрать модуль именно под встроенный в ragemp-server рантайм без экспериментов нет — проще и предсказуемее избегать нативных зависимостей там, где есть чистый JS-аналог:
| Нативный модуль | Чистый JS-аналог |
|---|---|
bcrypt | bcryptjs |
sqlite3 | better-sqlite3 (тоже нативный, но лучше sql.js или переход на MySQL/PostgreSQL) |
Для работы с MySQL хорошая новость — и mysql, и mysql2 (более новый, с поддержкой промисов) написаны на чистом JavaScript без нативных биндингов, так что типичный стек RP-гейммода "Node + MySQL" сам по себе безопасен в этом смысле. Проблема обычно возникает точечно, когда кто-то добавляет модуль для хеширования паролей или локальной БД, не проверив, что он нативный.
Как локализовать проблемный package методом исключения
Если гейммод состоит из нескольких пакетов и трейс в консоли не даёт прямого ответа (например, ошибка глубоко внутри общей библиотеки, подключённой из нескольких мест), быстрее всего сузить круг подозреваемых вручную:
- Временно вынесите все подпапки из
packages/, кроме одной, в соседнюю директорию (packages_disabled/) — RAGE:MP просто не найдёт их и не будет требовать. - Запустите сервер (
./ragemp-server) и посмотрите, стартует ли он с одним пакетом. - Возвращайте пакеты по одному, перезапуская сервер после каждого — как только ошибка вернулась, виновник — последний добавленный package.
cd ~/ragemp-srv
mkdir -p packages_disabled
mv packages/* packages_disabled/
mv packages_disabled/core packages/
./ragemp-server
# работает — возвращаем следующий
mv packages_disabled/gamemode packages/
./ragemp-server
Это грубее, чем чтение стектрейса, зато железно работает даже когда ошибка "размазана" по нескольким файлам через цепочку require, и стектрейс указывает на общую утилитарную функцию, а не на исходную причину. После того как package найден, дальше уже применяются предыдущие разделы — смотрите, это MODULE_NOT_FOUND, синтаксис, порядок загрузки или несовпадение API.
Помните и про базовую вещь: RAGE:MP не подхватывает изменения в packages/ на лету — после любой правки, даже однострочной, сервер нужно полностью перезапускать, промежуточного restart имя_пакета, как в консоли FiveM, здесь нет.
Поднять сервер RAGE:MP за пару минут
Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.
Создать серверЧастые вопросы
Сервер не пишет вообще никакой ошибки, просто "зависает" на старте — в чём дело?
Проверьте, не пытается ли один из пакетов на старте сделать сетевой запрос без таймаута (например, подключение к БД, которая недоступна) — Node в этом случае не падает, а просто держит процесс живым в ожидании ответа. Логика с подключением к внешним сервисам должна оборачиваться в try/catch и иметь таймаут.
Ошибка ссылается на файл в node_modules, а не в моём коде — что делать?
Ищите строку в вашем index.js или обработчике, откуда вызывается упавшая библиотека — сам модуль почти всегда падает из-за неверных аргументов, которые вы ему передали, а не из-за собственного бага. Смотрите на пару строк выше по стеку, где заканчивается node_modules и начинается ваш код.
Почему один и тот же гейммод падал вчера, а сегодня без изменений в коде запускается нормально?
Часто это как раз про непредсказуемый порядок сканирования packages/, если у вас несколько независимых пакетов с неявной зависимостью друг от друга — см. раздел про порядок загрузки выше, решение — числовые префиксы папок или событие готовности вместо расчёта на порядок require.
Можно ли посмотреть версию RAGE:MP сервера, чтобы свериться с требованиями гейммода?
Да, версия обычно печатается в первых строках консоли сразу при запуске ./ragemp-server, до попытки загрузки пакетов — если гейммод требует конкретную версию, это первое, что стоит сверить.
Ошибка в client_packages/, а не в packages/ — тот же подход применим?
Отчасти: код в client_packages/ выполняется в браузере игрока (CEF), и его ошибки видны не в консоли сервера, а в консоли клиента (DevTools лаунчера RAGE:MP), но логика чтения JS-стектрейса та же самая — файл, строка, что именно undefined.