Об администрировании сервера

Сегодня, в эпоху облачных провайдеров и SaaS, управлять сервером самостоятельно — крайне нишевое занятие. Для этого нужно очень хорошо разбираться в Linux и его многочисленных системных компонентах, которые, к тому же, постоянно обновляются и меняются, из-за чего знания и опыт быстро устаревают. Я много лет пользуюсь Linux, но и то не знаю всех тонкостей, постоянно учусь. Сама система принципиально несложная и понятная, но в ней постоянно появляется что-то новое, какие-то дополнительные слои абстракции. Linux многолик, его можно использовать в самых разных контекстах — отсюда и сложность. Ну и, конечно, знать систему на уровне десктопа — это одно, а админить по SSH — слегка другое.

Не хочу никого пугать надуманными страшилками: в сервере нет ничего мистического, чаще всего это обычный Ubuntu, где все точно такое же, как и в десктопных версиях (только нет собственно десктопа). Умеешь пользоваться терминалом, можешь научиться и сервером управлять. Но сервер — это более специфическая среда, тут у вас не просто рут и «хомяк». Надо серьезно заботиться о правах доступа и постоянно думать о том, не под угрозой ли безопасность. На десктопе угрозы чаще всего условные — ваш комп никто извне не станет ломать, никому вы не нужны. А вот сервер попытаются запросто. Любой мало-мальски посещаемый сайт постоянно осаждают боты в попытках взломать или заспамить. Поэтому головным мозгом думать надо постоянно, взвешивая каждый свой шаг. chmod тут не для галочки.

Хорошая новость — Linux стабилен. Ядро падает крайне редко, я за всю жизнь ни разу не видел, чтобы система падала из-за проблемы именно в ядре. На десктопе все крахи из-за неправильно настроенных приложений. Чаще всего падает графическая подсистема (раньше это были «иксы» — X11, сейчас еще и Wayland), либо чудят драйверы. На сервере графики нет, и внешние устройства не подключаются, так что единожды запущенная система будет работать, пока какая-нибудь авария не случится. А сломать Linux нечаянно в процессе каких-то настроек — это надо иметь особую степень криворукости. Зато вот ломающиеся серверные приложения — это суровые будни админа! Софт под Linux встречается самого разного качества — Open Source же. Все всегда запускается на свой страх и риск. Можно очень легко попортить любые данные, если они не защищены от записи. Поэтому бэкапы надо делать регулярно. Никогда нельзя запускать никакие команды или скрипты, если не понимаешь, что они делают. На сервере все проблемы от бездумных действий. Если на десктопе обычно работаешь под непривилегированным пользователем, а sudo для административных действий — это исключение, то на сервере все наоборот: root — норма, работа под другими пользователями — исключение. Но постоянно приходится помнить: настраиваешь что-то из-под рута — готовься к ошибкам типа «permission denied». Создал из-под рута какой-нибудь файлик, забыл выставить нужные права — все, будешь полдня искать причину полома.

Скажу честно: я не большой фанат Docker, я предпочитаю все ставить самостоятельно прямо в систему. Докером пользуюсь только в тех случаях, когда надо что-то совсем уж экстраординарное и навороченное, в чем лень разбираться. Например, у меня в контейнере запущен Nextcloud. Да, лень — это вообще-то не самая уважительная причина в данном контексте, но и я не профессиональный админ, а просто юзер, которого жизнь заставила админить. Поэтому я и не адепт всей этой контейнерной религии — да и вообще не сторонник каких-либо определенных DevOps-практик. Мне кажется, чем проще, тем лучше. Если Docker упрощает жизнь — то хорошо, берем на вооружение, а если с ним все становится сложнее, то ну его.

Проще всего поставить классический комплект из веб-сервера, PHP и MySQL (или MariaDB). Справится абсолютный новичок. Даже сертификаты сегодня уже не обязательно самому ставить — Caddy делает это за вас. Нужно только немного понимать, как работает стек TCP/IP (впрочем, если вам только обычные сайты писать, то и полноценный сервер не нужен, достаточно shared-хостинга).

Наверное, у всех, кто что-то разрабатывал под веб, был период, когда хочется написать свою серверную программу. Так вот, это ровно до того момента, когда вы действительно сможете запустить ее на реальном сервере. Только тогда начинаешь понимать, что к чему в плане безопасности. Энтузиазм велосипедить отпадает, вырабатывается склонность ставить чужое, но надежное. Взять тот же WordPress — да, система неидеальная. Да, то и дело что-то там отваливается после обновлений. Да, плагины быстро устаревают. Но WP все равно лучше, чем самописный движок. Свое писать стоит, только если совсем уж нет никакого выхода, если у вас задача какая-то узкоспециализированная. Например, сложный CI/CD или игровой сервер.

Админство — совсем не творческое занятие, а жутко рутинное. Это постоянная правка всевозможных конфигов и написание скриптов, установка пакетов, управление Systemd, копание в логах, мониторинг портов и, конечно, лихорадочные поиски документации в Интернете. Запустили сервис, потестили, увидели проблему, остановили, фиксим. Потом еще раз, и еще, и еще… Главное, что я понял — лучше вообще не вкатываться во все это без особой насущной необходимости. Обычному человеку должно хватать бесплатных сервисов, предоставляемых корпорациями. Вот когда уже не хватает, и «хочется странного» — другое дело.

Например, мой путь в администрирование начался, когда понадобился личный/семейный VPN. Думал этим и ограничиться, но затем понеслось. Сервер — это мир больших соблазнов. Поставил одно, тут же хочется попробовать другое, ведь почти всем популярным сервисам есть self-hosted аналоги: можно поднять личные аналоги GitHub, Google Workspace, Spotify и бог знает чего еще. Настраивать и дорабатывать напильником все это можно бесконечно — есть маньяки, которые именно этим большую часть свободного времени и занимаются) Для меня идеал — один раз настроил и забыл, но, к сожалению, так получается далеко не всегда.

C3: неужели наконец-то нормальный C-подобный язык?

Современная замена C — это то, чего очень не хватает всем, кто пишет что-то для десктопа с нуля, не полагаясь на популярнные платформы типа веба, Java/.Net, скриптовых языков и т.д. До настоящего времени реального аналога C, можно сказать, не было вовсе (исключением является BetterC-режим D, но это все-таки не отдельный язык, а подмножество, и он не для «зашедших с улицы», а совсем наоборот — для самых опытных программистов на D). Go и Rust не предлагать! 🤣

И вот, захожу я, как обычно, в ньюсгруппу d.D и вижу ссылку на C3: https://c3-lang.org. «Продать» мне что-то новомодное сложно, но язык зацепил! Я тестировал еще не все фичи, но вот что понравилось практически сразу:

  • Компилятор довольно легко установить. Вначале, правда, выкачивается майкрософтовский SDK, но после этого компилятор становится самостоятельным и Студию не требует;
  • Не нужна отдельная система сборки, компилятор сам выполняет ее функции;
  • Простая и вменяемая система типов, полностью совместимая с C без костылей;
  • Векторы, матрицы и кватернионы в стандартной библиотеке — бесценно! Сразу уйма работы отпадает при написании игрового движка;
  • Вообще, в целом, читаемая и красивая стандартная либа. Открываешь любой модуль, и все понятно;
  • Управление памятью, чем-то похожее на то, что я делаю в dlib (аллокаторы, универсальный new). Сборщика мусора нет;
  • Красивая обработка ошибок со встроенным Optional и проверкой на null на уровне синтаксиса. Optional — это просто модификатор типа: если его использовать, то компилятор тупо не дает прочитать такую переменную без предварительного null-чекинга. Можно его делать самостоятельно, а можно просто использовать оператор «!!», тогда компилятор вставит проверку сам. Все выглядит очень просто и читаемо.

Конечно, многого пока не хватает — например, нет менеджера пакетов. Биндинги к некоторым популярным библиотекам есть тут, но я пока не разобрался, как они линкуются, документации по этой теме нет. Нативных библиотек, ясное дело, тоже выбор пока совсем минимальный. Еще один жирный минус — не поддерживается компиляция в bare metal, а без этого язык нельзя назвать системным, и стопроцентной заменой C он считаться, строго говоря, не может.

Обновления

BindbC-Assimp

Биндинг к Assimp 5 размещен на GitHub и доступен в качестве DUB-пакета. К сожалению, название bindbc-assimp занято заброшенным и несуществующим ныне проектом, поэтому пришлось зарегать как bindbc-assimp5.

Оптимизация блога

Я настроил объектный кэш на основе Redis, а также добавил HTML-кэш и заголовок Cache-Control, что заметно ускорило загрузку страниц блога. Было исправлено множество мелких проблем верстки. Все архивные игры теперь размещены на одном сервере с блогом.

Тема, которую я использую, теперь также доступна на GitHub. Это форк WPEX Blogger 1.2 от WPExplorer, в котором я внес исправления для совместимости с PHP 8, исправил поиск в мобильном меню, добавил новые переводы на русский, а также внес патч, исправляющий совместимость с плагином Code Syntax Block.

Поддержка Assimp

Библиотека Assimp — относительно тяжеловесное, но самое функциональное решение для загрузки 3D-моделей. У меня наконец-то дошли руки добавить поддержку Assimp 5+ в Dagon как расширение dagon:assimp, благодаря чему можно будет использовать в движке модели форматов FBX, Collada, 3DS и многих других. Пока загружаются только меши, но в планах добавить поддержку материалов, узлов и анимации.

Перед загрузкой модели можно задать кастомные флаги постпроцессора:

AssimpAsset aModel;

override void beforeLoad()
{
    aModel = this.addAssimpAsset("assets/cacodemon.fbx");
    aModel.loaderOption =
        aiPostProcessSteps.Triangulate | aiPostProcessSteps.FlipUVs;
}

После этого можно использовать меши из массива aModel.meshes для рендеринга:

auto e = addEntity();
e.drawable = aModel.meshes[0];

Новый блог

Я со своим блогом вынужден переехать во второй раз. К сожалению, Hostingru, на котором я уже долгое время держу большинство своих сайтов, стремительно скатился во второсортный хостинг с чуть ли не еженедельными даунтаймами. Сломанный FTP, потери данных, проблемы с доступом к cPanel — с 2019 года я повидал все. Решил перейти на нидерландский VPS с Linux, на котором у меня уже почти год как крутятся облачные сервисы, связанные с персональным брендом PixelPerfect — доволен абсолютно, никаких проблем ни разу не испытывал. В связи с этим адрес меняется на https://blog.pixperfect.online/.

Спешу заверить, что тематика блога радикально не меняется — я все так же буду писать о своем опыте разработки на D и комментировать то, что делаю на GitHub. Но PixelPerfect — это больше, чем геймдев: отныне мой блог станет универсальным домом для всех моих профессиональных интересов.

По состоянию на 30.05.25 все материалы перенесены, причем удалось даже пофиксить проблемы с битыми изображениями в самых старых постах. Дизайн остался без изменений, я только поменял плагин WordPress для просмотра изображений и пофиксил строку поиска в мобильном режиме. Также слегка изменилась структура главного меню и добавился календарь постов.

В ближайшее время напишу отдельный пост о своих впечатлениях по настройке и администрированию linux-сервера — это оказалось очень интересно, хотя и не просто.

Новый сайт Dagon

Серьезно обновил https://gecko0307.github.io/dagon — реализован новый движок на основе рендеринга Markdown-документов, сайт переверстан в более современном дизайне, добавлены уроки, появилась отдельная страница со списком фич и скриншотами. Напомню, справка по API теперь живет на том же сайте: https://gecko0307.github.io/dagon/doc/dagon.html.

Написал новые уроки:

Также обновил урок Tutorial 11. Exporting Assets from Blender — там теперь подробности по экспорту из Blender и загрузке в Dagon моделей glTF.

Dagon 0.23.0

Обновил Dagon. Релиз не содержит нововведений и, в основном, оптимизирующий: теперь объекты Shader хранят прямые ссылки на параметры и обращаются по ним вместо строковых имен, когда записывают данные в методе bind. Индексы субрутин GLSL также извлекаются один раз при инициализации шейдера. Эта оптимизация дала весьма ощутимый прирост производительности на стороне CPU.

Удалено расширение dagon:stbi, так как стандартный загрузчик текстур Dagon полностью его заменяет. bindbc-sdl обновлен до версии 1.5.2.

К движку наконец-то появилась онлайн-документация — она доступна здесь. Документация генерируется из исходников. Полностью задокументированы пакеты dagon.core, dagon.graphics, dagon.resource, dagon.game и dagon.ui.

Лаунчер для Electronvolt

Майские праздники не прошли даром: на пару дней я основательно засел за Python и написал собственный лаунчер. Зачем? Он нужен для того, чтобы не привязывать игру к конкретной игровой платформе. В моем случае, я использую GameJolt, но такой подход работает для любого сервиса. Лаунчер служит посредником между игрой и API площадки. Все, что нужно делать игре — это сообщать лаунчеру о событиях наподобие «игрок получил такую-то ачивку». Лаунчер, в свою очередь, передает эти данные игровому сервису. Если надо переехать на другой сервис, достаточно просто обновить лаунчер, а игру патчить не придется — это удобно и экономит массу времени.

Для создания интерфейса я использовал WebView. Можно спорить до хрипоты о недостатках веб-стека, но на сегодняшний день он остается самым простым и универсальным решением для создания GUI. Нативные тулкиты платформозависимы, требуют соблюдения особой архитектуры приложения и чаще всего выглядят скучновато, а браузерный движок позволяет буквально за несколько часов наваять красивые формы с анимациями и спецэффектами — сделал и забыл!

Долгое время я писал веб-интерфейсы на CEF (Chromium Embedded Framework) — «Electron для Python». Он предоставляет portable браузерный движок и в целом свою задачу решает, но имеет ряд проблем. CEF Python уже несколько лет как не развивается, и версия Chromium в нем заметно устарела, новейшие свойства CSS не поддерживаются. Кроме того, в собранном виде CEF-приложение весит около 200 Мб, что для небольшого приложения, мягко говоря, жирновато. Все эти проблемы я решил переходом на PyWebView! Единственное, чем придется пожертвовать — совместимостью с Windows 8, что в 2025 году совсем не критично. Собранный PyInstaller’ом, лаунчер стал весить менее 50 Мб (используя UPX, можно сжать еще сильнее). А код под PyWebView гораздо компактнее и проще, чем под CEF Python.

Основная задача моего лаунчера — служить посредником между игрой и внешними сервисами. Для этого используется механизм IPC: игра отправляет события лаунчеру через TCP-соединение по кастомному протоколу. Например, когда игрок получает достижение, игра отправляет команду типа "eV:award=AchievementName". Лаунчер, получив такую команду, передает данные на сервер.

Важная особенность этого механизма — асинхронность: блокировать игровой цикл, чтобы отправить что-то по сети — не комильфо, задержки бывают в секунду и даже дольше. Выручил taskPool из std.parallelism — все вызовы IPC оборачиваются в task и заносятся в очередь пула задач:

taskPool.put(task!ipcSendSync(message));

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

GameJolt имеет простой и удобный API, который легко интегрировать в проект. Однако, чтобы не компрометировать приватный ключ, я решил не внедрять его напрямую в игру. Вместо этого ключ хранится на моем сервере, который и отправляет запросы API. Лаунчер напрямую с API не взаимодействует — он общается только с сервером. Пользователь может по желанию авторизоваться в лаунчере, используя логин и токен GameJolt — токен был специально придуман, чтобы не сообщать играм пароль от аккаунта. В случае успешной авторизации достижения в игре будут синхронизироваться с аккаунтом.

Лаунчер также позволяет пользователю настроить графику игры перед запуском. Для этого он взаимодействует с конфигурационным файлом игры — settings.conf. Лаунчер считывает текущие настройки, обновляет их в зависимости от выбранных пользователем параметров и сохраняет изменения обратно в файл. Также он мониторит изменения в конфиге — если игра обновила файл, интерфейс лаунчера подхватит изменения.

На будущее есть план реализовать систему автообновления (полезно для игроков, которые не используют игровые платформы и предпочитают устанавливать игры вручную). Кроме того, можно добавить установку модов.

Dagon 0.22.0

Вышла новая версия движка! Как уже было упомянуто, добавил поддержку скелетной анимации glTF — спасибо denizzzka за реализацию модуля dagon.resource.gltf.animation с необходимыми классами GLTFAnimationSampler, GLTFAnimationChannel и GLTFAnimation. Скелетная анимация постепенно будет интегрирована в ядро движка: уже добавил абстрактный класс Pose для хранения матриц костей и необходимую функциональность для их передачи в вершинные шейдеры во всех стандартных пайплайнах Dagon. Вычисление этих матриц зависит от конкретного анимационного воркфлоу, для glTF предусмотрены GLTFPose и GLTFBlendedPose.

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

В движок добавлен встроенный логгер — dagon.core.logger, который позволяет делать записи функциями logInfo, logDebug, logError и logFatalError. Лог по умолчанию выводится в консоль, можно также включить вывод в файл. Функции логирования работают аналогично writeln — то есть, поддерживают вариативные параметры любых типов. Можно отключить записи ниже минимального уровня, изменив глобальную переменную logLevel (по умолчанию — LogLevel.All).

Улучшения в dagon.core.event: горячее подключение игрового контроллера и поддержка вибрации (EventManager.gameControllerRumble).

Добавлен новый примитив — конус (ShapeCone).

glTF: скелетная анимация

В следующей версии Dagon наконец-то дебютирует поддержка анимации в сценах glTF. Поддерживается GPU-скиннинг, причем универсальный — для любого объекта можно передать в рендер-пайплайн набор матриц костей (за это отвечает объект Pose) и необходимые вершинные атрибуты (индексы матриц костей и веса).

Поскольку система низкоуровневая, скиннинг требует подготовительной работы — в частности, под каждый уникальный анимированный меш нужен объект позы (Pose), который анимирует кости и заполняет массив матриц. Позу нужно применить к сущности (Entity), и тогда в вершинном шейдере меш этой сущности будет трансформирован матрицами, хранящимися в позе. Преимущество в том, что можно расширять систему скиннинга практически без ограничений. Если вы понимаете, как устроен формат glTF, вы можете написать кастомную альтернативу стандартному классу GLTFPose с любыми фичами: процедурная анимация, обратная кинематика — все, что угодно.

auto characterNode = characterModel.node("character");
characterPose = New!GLTFPose(characterNode.skin, assetManager);
characterPose.animation = characterModel.animation("walk");

Entity character = characterNode.entity;
character.pose = characterPose;
characterPose.play();

Можно прикреплять объекты к костям при помощи родительской связи:

Entity weapon = addEntity(characterModel.node("arm_right").entity);
weapon.drawable = someWeaponMesh;