Рубрика: Заметки

  • Бесплатные модели для OpenCode через FreeLLMAPI

    OpenCode — открытый агент для программирования, который работает в терминале: читает файлы проекта, правит код, запускает команды и тесты, ищет по репозиторию и работает с git. Он не привязан к одному поставщику моделей, поэтому к нему можно подключить почти любой OpenAI-совместимый сервис.

    FreeLLMAPI — открытый self-hosted роутер, который собирает бесплатные тарифы десятков AI-провайдеров за одним OpenAI-совместимым адресом /v1. Вместо тридцати ключей, тридцати лимитов и тридцати мест, где запрос может упасть, у вас один локальный шлюз и один unified-ключ. Роутер сам выбирает доступную модель, переключается на следующую при ошибке 429 или 5xx и следит, чтобы запросы не выходили за квоты бесплатных тарифов.

    В этой заметке я опишу установку FreeLLMAPI, добавление провайдеров и подключение шлюза к OpenCode.

    Что понадобится

    Для работы нужны четыре вещи:

    • Установленный OpenCode. Если агент ещё не стоит, порядок установки я разбирал в отдельной заметке про OpenCode и DeepSeek.
    • Запущенный FreeLLMAPI. Проще всего поставить десктопное приложение или контейнер Docker.
    • Node.js 20.18 или новее. Он нужен для CLI-генератора, который настраивает OpenCode. Сам OpenCode может быть установлен и без Node.
    • Ключи хотя бы одного провайдера. Подойдут бесплатные тарифы Google AI Studio, Groq, Cerebras, Mistral, OpenRouter, NVIDIA, HuggingFace и других сервисов из каталога.

    Установка FreeLLMAPI

    У проекта есть несколько путей установки. Выбирайте тот, который удобнее для вашей системы.

    Десктопное приложение

    Самый простой вариант для Windows и macOS — готовый установщик со страницы релизов. В Windows это .exe, в macOS — .dmg для Apple Silicon или Intel. Приложение живёт в трее и поднимает роутер вместе с панелью управления локально.

    Пароль для десктопной версии не нужен: панель входит сама через скрытую локальную учётку. Единственные учётные данные, которые вам понадобятся, — unified-ключ из всплывающего окна в трее или с заголовка страницы Keys.

    Данные приложения лежат в одном каталоге:

    • Windows — %APPDATA%\FreeLLMAPI\
    • macOS — ~/Library/Application Support/FreeLLMAPI/
    • Linux — ~/.config/FreeLLMAPI/

    Docker

    Если Docker уже установлен, сервер поднимается одной командой. Скрипт создаёт каталог ~/freellmapi, генерирует ключ шифрования, скачивает образ и запускает контейнер:

    curl -fsSL https://freellmapi.co/install.sh | bash
    

    Повторный запуск скрипта безопасен: файл .env и ключ шифрования сохраняются, а контейнер обновляется до свежей версии. В Windows этот способ работает в WSL или в любом bash-окружении.

    После запуска панель открывается по адресу:

    http://localhost:3001
    

    Для серверной установки нужно создать учётную запись с email и паролем. Если пароль забыт, код сброса печатается в логе контейнера:

    docker compose logs -f freellmapi
    

    Docker Compose

    Если хочется держать конфигурацию в git и управлять запуском вручную, подойдёт обычный Compose. В macOS и Linux:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    ENCRYPTION_KEY="$(openssl rand -hex 32)"
    printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env
    docker compose up -d
    

    В Windows PowerShell:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    $Bytes = New-Object Byte[] 32
    [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($Bytes)
    $ENCRYPTION_KEY = -join ($Bytes | ForEach-Object { "{0:x2}" -f $_ })
    "ENCRYPTION_KEY=$ENCRYPTION_KEY`nPORT=3001" | Out-File -Encoding utf8 .env
    docker compose up -d
    

    Ключ ENCRYPTION_KEY обязателен: им шифруются ключи провайдеров в базе SQLite. Менять его на лету нельзя — старые секреты перестанут расшифровываться. Для ротации в проекте есть отдельная команда rotate-encryption-key.

    Запуск из исходников

    Для разработки или отладки можно запустить проект через Node.js:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    npm install
    ENCRYPTION_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')"
    printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env
    npm run dev
    

    В этом режиме API и панель поднимаются на порту 3001, а Vite-интерфейс — на 5173.

    Первый запуск и ключи провайдеров

    Открываем панель, заходим на страницу Keys и добавляем ключи тех сервисов, которыми пользуемся. Для каждого провайдера в интерфейсе есть ссылка на регистрацию и подсказка, где взять ключ. Начать достаточно с одного-двух: например, Google AI Studio и Groq.

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

    Дальше стоит зайти на страницу Models и посмотреть, какие модели доступны. Там же настраивается стратегия маршрутизации и порядок Fallback Chain — цепочки, по которой роутер идёт при ошибках и лимитах. Модель auto означает, что выбор делает сам роутер. Кроме неё доступны варианты вроде auto:fast, auto:smart и именованные профили под конкретные задачи.

    Отдельно стоит проверить, что в цепочке есть модели с поддержкой инструментов. OpenCode активно использует tool calling, и модель без этой возможности не сможет нормально читать файлы и запускать команды.

    Unified-ключ берётся там же, на странице Keys, либо в трее десктопного приложения. Он выглядит как строка с префиксом:

    freellmapi-...
    

    Именно этот ключ, а не ключи отдельных провайдеров, указывается в OpenCode.

    Подключение к OpenCode

    У FreeLLMAPI есть генератор конфигурации, который сам читает каталог моделей с запущенного шлюза, делает резервную копию существующего конфига и аккуратно добавляет в него провайдера.

    Сначала выставим unified-ключ переменной окружения. Генератор читает её вместо отдельного флага, поэтому секрет не попадёт в историю команд.

    В macOS и Linux:

    export FREELLMAPI_API_KEY=freellmapi-YOUR_KEY
    

    В Windows PowerShell:

    $env:FREELLMAPI_API_KEY="freellmapi-YOUR_KEY"
    

    Теперь смотрим, что именно будет записано:

    npx freellmapi setup-opencode --url http://localhost:3001 --dry-run
    

    Если diff устраивает, повторяем команду без флага:

    npx freellmapi setup-opencode --url http://localhost:3001
    

    Генератор пишет глобальный конфиг OpenCode:

    ~/.config/opencode/opencode.json
    

    В нём появляется провайдер freellmapi с пакетом @ai-sdk/openai-compatible, базовым адресом http://localhost:3001/v1 и картой моделей из живого каталога. Моделью по умолчанию становится freellmapi/auto.

    Сам ключ в файл не попадает: в конфиге стоит подстановка {env:FREELLMAPI_API_KEY}. Поэтому переменная должна оставаться выставленной и при запуске агента:

    opencode
    

    Такой способ удобен ещё и тем, что секрет не оседает в конфиге и не попадёт в git. Для CI и одноразовых запусков это правильнее, чем хранить ключ в файле.

    Ручная настройка

    Если не хочется запускать генератор, провайдера можно описать в opencode.json вручную. Минимальный вариант выглядит так:

    {
      "$schema": "https://opencode.ai/config.json",
      "model": "freellmapi/auto",
      "provider": {
        "freellmapi": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "FreeLLMAPI",
          "options": {
            "baseURL": "http://localhost:3001/v1",
            "apiKey": "{env:FREELLMAPI_API_KEY}"
          },
          "models": {
            "auto": {
              "name": "FreeLLMAPI Auto Router",
              "limit": { "context": 1048576, "output": 65536 }
            }
          }
        }
      }
    }
    

    Поле model на верхнем уровне важно: без него OpenCode может продолжить использовать прежнюю модель или не выбрать ничего на свежей установке. Лимиты контекста и вывода лучше брать из ответа шлюза, а не придумывать:

    curl http://localhost:3001/v1/models
    

    В список models можно добавить конкретные идентификаторы, если нужно переключаться на них вручную. Но для повседневной работы достаточно auto: роутер сам подберёт живую модель и переключится при лимите.

    Как этим пользоваться

    После запуска opencode список моделей открывается знакомой командой:

    /models
    

    В нём будет провайдер FreeLLMAPI и модель auto. Дальше работа идёт как обычно: описываем задачу, указываем файлы через @, переключаем режим планирования клавишей Tab, откатываем правки командой /undo. Для нового проекта полезно выполнить /init, чтобы агент создал AGENTS.md.

    Пара практических приёмов:

    • Маршрутизация. Для рутины оставляйте auto. Если нужна скорость, попробуйте auto:fast, если качество — auto:smart или конкретную сильную модель из каталога.
    • Диагностика. Каждый ответ шлюза содержит заголовок X-Routed-Via с именем провайдера и модели, которые реально обслужили запрос. По нему видно, куда роутер отправил задачу.
    • Аналитика. На странице Analytics в панели видны объём запросов, успешность, задержки p50/p95 и расход токенов по провайдерам.
    • Непрерывность диалога. При переключении модели посреди разговора шлюз может добавить короткую заметку о передаче контекста. Включается переменной FREELLMAPI_CONTEXT_HANDOFF=on_model_switch.

    Если OpenCode запущен в контейнере, а FreeLLMAPI — на хосте, адрес localhost будет указывать на сам контейнер. В этом случае в baseURL нужно использовать host.docker.internal или другой доступный из контейнера адрес.

    На что обратить внимание

    FreeLLMAPI — это не замена платным моделям и не сервис с SLA. Это способ собрать бесплатные тарифы в один аккуратный шлюз. Отсюда несколько ограничений:

    • Качество плавает. Бесплатные тарифы заканчиваются, и поздно вечером, ближе к исчерпанию дневных квот, роутер может уходить на модели слабее. Квоты обычно сбрасываются в полночь по UTC.
    • Задержки нестабильны. Разные провайдеры отвечают по-разному, а при переключении на резервную модель запрос может занять заметно больше времени.
    • Флагманских моделей нет. В бесплатных тарифах доступны хорошие, но не топовые модели. Для сложной архитектуры это стоит учитывать.
    • Шлюз должен быть запущен. Если приложение или контейнер остановлены, OpenCode не получит ответа. Автозапуск приложения в трее или рестарт политики контейнера решают эту проблему.
    • Проект рассчитан на одного пользователя. Ключи провайдеров хранятся локально и шифруются, но общая панель на несколько человек — не сценарий этого инструмента. Unified-ключ и .env стоит беречь так же, как обычный секрет.

    И ещё один нюанс: каталог моделей обновляется сам, но бесплатная установка получает снимок каталога с задержкой около тридцати дней. Платный тариф Premium ускоряет обновление, однако для знакомства с проектом он не нужен — сам роутер остаётся бесплатным и открытым.

    Ссылки

    https://github.com/tashfeenahmed/freellmapi
    https://freellmapi.co/
    https://freellmapi.co/models
    https://github.com/tashfeenahmed/freellmapi/releases/latest
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/install/01-install.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/01-agent-clients.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/cli/README.md
    https://opencode.ai/
    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/

    Источники

    https://github.com/tashfeenahmed/freellmapi
    https://github.com/tashfeenahmed/freellmapi/blob/main/README.ru.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/install/01-install.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/01-agent-clients.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/02-supported-agents.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/cli/README.md
    https://opencode.ai/docs/providers/

  • Российские AI-провайдеры для OpenCode

    OpenCode — открытый агент для программирования, который работает прямо в терминале: читает файлы проекта, правит код, запускает команды и тесты, ищет по репозиторию и работает с git. Его сильная сторона в том, что он не привязан к одному поставщику моделей и умеет подключать десятки провайдеров.

    GigaChat — большая языковая модель от Сбера. В готовом списке провайдеров OpenCode её нет, но это не проблема: OpenCode подключает произвольных провайдеров через пакеты Vercel AI SDK. Для GigaChat есть пакет gigachat-ai-sdk-provider, который берёт на себя авторизацию по OAuth и обновление токена доступа. Отдельный нюанс — сертификаты Минцифры: без них Node не доверяет серверам GigaChat, и запросы падают с ошибкой проверки сертификата.

    В этой заметке я опишу, как получить ключ, поставить сертификаты и подключить GigaChat к OpenCode.

    Что понадобится

    Для работы нужны три вещи:

    • Установленный OpenCode. Если агент ещё не стоит, порядок установки я разбирал в отдельной заметке про OpenCode и DeepSeek.
    • Аккаунт GigaChat. Регистрация в GigaChat Studio и ключ авторизации — для входа понадобится Sber ID.
    • Современный терминал. Подойдёт WezTerm, Alacritty, Ghostty, Kitty или любой другой.

    Ключ авторизации GigaChat

    Ключ создаётся в личном кабинете GigaChat Studio. Заходим на developers.sber.ru/studio, авторизуемся и создаём проект в разделе GigaChat API. В настройках проекта есть блок с авторизационными данными: строка, которую оттуда копируют, — это уже готовый ключ авторизации в base64. Именно он подставляется в переменную окружения, а не пара клиент-секрет по отдельности.

    Вместе с ключом задаётся scope — область доступа:

    • GIGACHAT_API_PERS — для физических лиц.
    • GIGACHAT_API_B2B — для ИП и юридических лиц.
    • GIGACHAT_API_CORP — корпоративный доступ.

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

    Важно понимать, что это за строка. Ключ авторизации — это не отдельный секрет, а уже закодированная в base64 пара client_id:client_secret. Библиотека gigachat-js подставляет его в OAuth-запрос заголовком:

    Authorization: Basic ваш_ключ_авторизации
    

    В ответ приходит JWE-токен доступа со сроком жизни около получаса, и дальше библиотека обновляет его автоматически. Пакет сам проверяет, что значение похоже на base64, и предупреждает, если в переменную случайно попал «сырой» client secret. Поэтому в конфиг и переменную окружения нужно передавать именно готовую base64-строку из кабинета, а не пару client_id и client_secret по отдельности.

    Сертификаты Минцифры

    GigaChat API использует сертификаты НУЦ Минцифры. В стандартном хранилище доверенных корней их нет, поэтому при попытке получить токен доступа запрос завершается ошибкой вида:

    self-signed certificate in certificate chain
    

    Сертификаты можно поставить на уровне операционной системы — тогда им будут доверять браузер и системные утилиты. Но OpenCode работает на Node и Bun, поэтому надёжнее и проще указать файл сертификата прямо в переменной NODE_EXTRA_CA_CERTS. Скачаем корневой и выпускающий сертификаты и сложим их в один PEM-файл:

    curl -s https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt -o russian_trusted_root_ca_pem.crt
    curl -s https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.crt -o russian_trusted_sub_ca_pem.crt
    cat russian_trusted_root_ca_pem.crt russian_trusted_sub_ca_pem.crt > russian_trusted_ca_bundle.pem
    

    Файл можно положить в удобное место и указывать его полный путь при запуске агента.

    Тут есть подвох. Скачанные файлы используют переводы строк CRLF, а у корневого сертификата нет завершающего перевода строки. Поэтому обычный cat склеивает конец первого сертификата и начало второго в одну строку:

    -----END CERTIFICATE----------BEGIN CERTIFICATE-----
    

    LibreSSL — а системный openssl на macOS, напомню, это именно LibreSSL — такой файл не принимает и отвечает ошибкой:

    PEM routines:CRYPTO_internal:bad end line
    

    Значит, одним cat не обойтись. Каждый сертификат нужно сначала прогнать через openssl: он перекодирует его в канонический PEM с переводами строк LF и завершающим переводом строки, и только потом склеивать:

    openssl x509 -in russian_trusted_root_ca_pem.crt -out russian_trusted_root_ca.pem
    openssl x509 -in russian_trusted_sub_ca_pem.crt -out russian_trusted_sub_ca.pem
    cat russian_trusted_root_ca.pem russian_trusted_sub_ca.pem > russian_trusted_ca_bundle.pem
    

    Проверить, что файл читается, можно так:

    openssl x509 -in russian_trusted_ca_bundle.pem -noout -subject
    

    Если сертификаты взяты из другого источника и приходят в формате DER или PKCS#7, их тоже достаточно перекодировать в PEM:

    openssl x509 -in cert.crt -inform DER -outform PEM -out cert.pem
    openssl pkcs7 -print_certs -in bundle.p7b -out cert.pem
    

    Подключение к OpenCode

    Провайдер описывается в конфигурационном файле opencode.jsonc. OpenCode сам скачает и подключит npm-пакет, указанный в поле npm, найдёт в нём фабрику createGigaChat и передаст ей опции. Достаточно перечислить модели по их идентификаторам из API.

    Файл можно разместить в двух местах:

    • Глобально — ~/.config/opencode/opencode.jsonc. Настройки действуют для всех проектов пользователя.
    • В проекте — opencode.jsonc в корне проекта. Такой конфиг имеет больший приоритет и его безопасно коммитить в git.

    Оба файла используют одну схему и объединяются: проектный перекрывает глобальный только по совпадающим ключам, остальные настройки сохраняются. Поддерживается и расширение .jsonc. Если GigaChat нужен лишь в части репозиториев, удобнее держать провайдера в проектном конфиге, а не в глобальном.

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "gigachat": {
          "npm": "gigachat-ai-sdk-provider",
          "name": "GigaChat",
          "models": {
            "GigaChat-2-Max": { "name": "GigaChat 2 Max" },
            "GigaChat-2-Pro": { "name": "GigaChat 2 Pro" },
            "GigaChat-2": { "name": "GigaChat 2 Lite" },
            "GigaChat": { "name": "GigaChat" }
          }
        }
      }
    }
    

    Здесь перечислены модели, доступные персональному ключу. Если у вас проект для ИП или юрлица с доступом к GigaChat 3, добавьте в блок models нужные идентификаторы из метода models.

    Сам ключ удобнее не хранить в файле, а передавать переменной окружения: пакет gigachat-js читает её автоматически. Значение scope тоже можно задать переменной. Осталось выставить путь к сертификатам и запустить агент:

    export GIGACHAT_CREDENTIALS=ваш_ключ_авторизации
    export GIGACHAT_SCOPE=GIGACHAT_API_PERS
    export NODE_EXTRA_CA_CERTS=/путь/к/russian_trusted_ca_bundle.pem
    opencode
    

    Такой вариант хорош ещё и тем, что секрет не оседает в локальном хранилище auth.json: он живёт только в окружении процесса. Для CI и одноразовых запусков это удобнее.

    Модели

    Набор доступных моделей зависит от scope ключа. Персональному ключу (GIGACHAT_API_PERS) доступно семейство GigaChat и GigaChat 2:

    • GigaChat — базовая модель.
    • GigaChat-2 — быстрая и лёгкая модель для повседневных задач, в интерфейсе отображается как Lite.
    • GigaChat-2-Pro — усовершенствованная модель для ресурсоёмких задач.
    • GigaChat-2-Max — самая мощная из доступных по персональному ключу.

    Семейство GigaChat 3 (GigaChat-3-Ultra, GigaChat-3-Pro, открытые модели вроде GigaChat3.5-432B-A28B-Reasoning) видно в каталоге консоли, но персональному ключу оно недоступно: для него нужен проект ИП или юрлица со scope GIGACHAT_API_B2B или GIGACHAT_API_CORP и подходящим тарифом. На персональном ключе такая модель отвечает ошибкой:

    {"status":404,"message":"No such model"}
    

    Проверить, что в реальности отдаёт ваш ключ, можно запросом к списку моделей:

    curl -H "Authorization: Bearer <токен_доступа>" https://gigachat.devices.sberbank.ru/api/v1/models
    

    Ещё два нюанса. Идентификаторы моделей в API не совпадают с отображаемыми названиями — лёгкая модель вызывается как GigaChat-2, а не GigaChat-2-Lite. И каталог в консоли показывает не то же самое, что доступно вашему ключу, поэтому ориентироваться стоит на ответ метода models, а не на список с ценами.

    В интерфейсе список моделей открывается командой:

    /models
    

    Для рутины в репозитории — навигации по файлам, мелких правок, запуска тестов — вполне хватает GigaChat-2. На сложные задачи и проектирование имеет смысл переключаться на Pro или Max.

    Первый запуск в проекте

    Переходим в каталог проекта и запускаем агента:

    cd ваш_проект
    opencode
    

    Первым делом полезно инициализировать агент:

    /init
    

    OpenCode проанализирует структуру проекта и создаст файл AGENTS.md — инструкции для агента. Этот файл стоит закоммитить в git: он помогает агенту понимать принятые в проекте соглашения и паттерны.

    Альтернатива: локальный прокси

    Если по каким-то причинам не хочется тянуть npm-провайдер, тот же результат даёт локальный прокси. У команды GigaChat есть официальный gpt2giga — FastAPI-сервис, который транслирует запросы в формате OpenAI, Anthropic и Gemini в GigaChat API и сам обновляет токен доступа. Он поднимается локально на порту 8090, после чего в OpenCode настраивается обычный OpenAI-совместимый провайдер через пакет @ai-sdk/openai-compatible с базовым адресом http://localhost:8090/v1. Минус этого пути в том, что рядом с OpenCode нужно держать ещё и запущенный Python-сервис.

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "gigachat": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "GigaChat (proxy)",
          "options": {
            "baseURL": "http://localhost:8090/v1"
          },
          "models": {
            "GigaChat-2-Max": { "name": "GigaChat 2 Max" }
          }
        }
      }
    }
    

    DeepSeek и другие модели через Cloud.ru

    У самого GigaChat API моделей DeepSeek нет: там только семейство GigaChat и модели для эмбеддингов. Если в OpenCode нужен именно DeepSeek, но через российский облачный шлюз, подойдёт сервис Foundation Models от Cloud.ru. Он отдаёт модели по OpenAI-совместимому протоколу, поэтому подключается штатным пакетом @ai-sdk/openai-compatible.

    В каталоге Cloud.ru Foundation Models есть, например, такие модели:

    deepseek-ai/DeepSeek-V4.1-Flash
    deepseek-ai/DeepSeek-V4-Flash
    deepseek-ai/DeepSeek-V4-Pro
    

    Ключ выпускается в консоли Cloud.ru: раздел Пользователи, вкладка Сервисные аккаунты. Создаём сервисный аккаунт уровня проекта, затем в его учётных данных создаём API-ключ с сервисом Foundation Models. Секрет ключа показывается один раз — его и сохраняем.

    Конфигурация провайдера в opencode.json выглядит так:

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "cloudru": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "Cloud.ru Foundation Models",
          "options": {
            "baseURL": "https://foundation-models.api.cloud.ru/v1",
            "apiKey": "{env:CLOUDRU_API_KEY}"
          },
          "models": {
            "deepseek-ai/DeepSeek-V4.1-Flash": { "name": "DeepSeek V4.1 Flash", "limit": { "context": 1048576, "output": 1048576 } },
            "deepseek-ai/DeepSeek-V4-Flash": { "name": "DeepSeek V4 Flash", "limit": { "context": 1048576, "output": 1048576 } },
            "deepseek-ai/DeepSeek-V4-Pro": { "name": "DeepSeek V4 Pro", "limit": { "context": 1048576, "output": 1048576 } }
          }
        }
      }
    }
    

    Ключ передаём переменной окружения:

    export CLOUDRU_API_KEY=ваш_ключ_cloudru
    opencode
    

    Сертификаты Минцифры здесь не нужны: у домена api.cloud.ru обычный TLS, в отличие от GigaChat. Зато стоит следить за балансом проекта. При нулевом счёте авторизация проходит, а запросы падают с ответом биллинга:

    {"message":"Not enough money"}
    

    Это не ошибка конфига, а сигнал, что нужно пополнить проект в консоли Cloud.ru.

    На что обратить внимание

    Пара практических моментов, которые чаще всего мешают при первой настройке:

    • Ошибка проверки сертификата. Сообщение про self-signed certificate в цепочке означает, что переменная NODE_EXTRA_CA_CERTS не подхватилась. Проверьте путь к файлу, то, что агент запущен в том же окружении, и сам файл: если при склейке двух сертификатов через cat их концы оказались в одной строке, LibreSSL вернёт bad end line, и bundle нужно пересобрать через openssl, как в разделе про сертификаты.
    • Ошибка 401. Как правило, это неверный ключ авторизации или несовпадающий scope — для физлица нужен GIGACHAT_API_PERS.
    • Ошибка 404 с текстом No such model. Это не сбой подключения, а отсутствие доступа к конкретной модели. Уберите её из конфига или замените на доступную. В сообщении вида GigaChat 404: Unknown error виноват тот же ответ GigaChat — провайдер просто не смог разобрать тело ошибки.
    • Cost. OpenCode не требует подписки: вы платите GigaChat напрямую по факту расхода токенов, поэтому длинные сессии предсказуемы по цене.
    • Качество модели. На сложных архитектурных задачах модели разных классов отличаются, поэтому для проектирования имеет смысл брать модель посильнее, а рутину отдавать быстрой.

    Ссылки

    https://opencode.ai/
    https://opencode.ai/docs/providers/
    https://developers.sber.ru/studio/
    https://developers.sber.ru/docs/ru/gigachat/guides/main
    https://github.com/nyddle/gigachat-ai-sdk-provider
    https://github.com/ai-forever/gpt2giga
    https://cloud.ru/docs/foundation-models/ug/topics/quickstart

    Источники

    https://opencode.ai/docs/providers/#custom-provider
    https://developers.sber.ru/docs/ru/gigachat/certificates
    https://developers.sber.ru/docs/ru/gigachat/models/main
    https://developers.sber.ru/docs/ru/gigachat/guides/selecting-a-model
    https://github.com/nyddle/gigachat-ai-sdk-provider
    https://github.com/ai-forever/gpt2giga
    https://cloud.ru/docs/foundation-models/ug/topics/quickstart
    https://cloud.ru/docs/foundation-models/ug/topics/overview__available__models

  • AirLLM: большие языковые модели на слабой видеокарте

    Обычно для запуска большой языковой модели на 70 миллиардов параметров нужно несколько десятков гигабайт видеопамяти. AirLLM подходит к этой задаче спокойнее и предлагает другой путь: такую модель можно запустить на видеокарте всего с 4 ГБ памяти, причём без квантизации, дистилляции и обрезки. В этой заметке я постараюсь без спешки рассказать, как устроен AirLLM, как им пользоваться, и поделюсь своим небольшим проектом AirLLM-experiments, который делает работу с этой библиотекой немного удобнее.

    Что такое AirLLM

    AirLLM — открытая библиотека для инференса больших языковых моделей, которую написал Gavin Li. Её идея проста и вместе с тем изящна: вместо того чтобы держать в видеопамяти все веса модели сразу, библиотека загружает на GPU только один слой за раз. Веса хранятся на диске в виде порезанных на слои шардов, а во время генерации каждый слой по очереди подгружается, отрабатывает и выгружается.

    Отсюда следует, что объём нужной видеопамяти зависит не от общего размера модели, а от размера одного её слоя. Именно поэтому модель на 70B укладывается в 4 ГБ, DeepSeek-V3 на 671B помещается примерно в 12 ГБ, а Qwen3-235B — примерно в 3 ГБ. А вот размер самой модели ограничивает в первую очередь место на диске, а не возможности видеокарты.

    За такую экономию памяти приходится платить скоростью: каждый слой читается с диска при генерации каждого токена, поэтому вывод идёт медленно, примерно в секунды на токен. Так что AirLLM — это скорее не про быстрый интерактивный чат, а про возможность и вовсе запустить модель, которая иначе не поместилась бы в железо.

    Установка

    Библиотека ставится из PyPI обычной командой:

    pip install airllm
    

    При первом запуске модель скачается из Hugging Face и будет разложена на слои. Процесс это небыстрый и занимает немало места на диске, поэтому свободное место стоит подготовить заранее. При желании можно включить сжатие 4bit или 8bit — тогда к весам применится блочная квантизация, которая ускоряет загрузку слоёв с диска примерно в три раза, а точность теряется совсем немного.

    Базовый пример

    Работать с AirLLM можно почти так же, как с обычной моделью из transformers. Достаточно один раз указать идентификатор модели на Hugging Face, а дальше всё будет знакомо:

    from airllm import AutoModel
    
    MAX_LENGTH = 128
    model = AutoModel.from_pretrained("Qwen/Qwen3-32B")
    
    input_text = ['What is the capital of United States?']
    
    input_tokens = model.tokenizer(
        input_text,
        return_tensors="pt",
        return_attention_mask=False,
        truncation=True,
        max_length=MAX_LENGTH,
        padding=False)
    
    generation_output = model.generate(
        input_tokens['input_ids'].cuda(),
        max_new_tokens=20,
        use_cache=True,
        return_dict_in_generate=True)
    
    print(model.tokenizer.decode(generation_output.sequences[0]))
    

    Класс AutoModel сам определяет тип модели, поэтому одну и ту же строку можно использовать и для Llama, и для Qwen, и для DeepSeek. А если взять модель вроде DeepSeek-V3 на 671B параметров, она тоже поместится примерно в 12 ГБ видеопамяти — во многом именно этим проект и привлекателен.

    Запуск на macOS

    AirLLM работает не только на CUDA, но и на Apple Silicon. На macOS он использует бэкенд MLX, поэтому понадобятся установленные mlx и torch, и поддерживаются только чипы Apple. Есть и небольшое ограничение: реализация MLX поддерживает лишь Llama-подобные архитектуры, так что Qwen и похожие модели на макбуке таким способом не запустить.

    Пример использования

    AirLLM-experiments — это две небольшие интерактивные консольные утилиты для запуска больших языковых моделей локально. Они избавляют от необходимости каждый раз писать один и тот же код для загрузки модели и организации чата.

    Первый инструмент, airllm-lib-usage.py, запускает библиотеку AirLLM прямо в текущем процессе Python. Он показывает нумерованный список моделей из общего каталога, при необходимости сам доустанавливает недостающие зависимости и открывает сессию чата. На macOS используется рантайм MLX, на остальных платформах — torch.

    Второй инструмент, airllm-openai-server-client.py, помогает работать с сервером airllm-openai-server, который запускается в Docker и предоставляет OpenAI-совместимый API. При первом запуске утилита спрашивает настройки, собирает образ, поднимает контейнер и открывает потоковый чат уже к работающему серверу. При повторных запусках она находит существующий контейнер и просто переиспользует его, а кеш моделей хранит в отдельном Docker-томе, чтобы не скачивать модель заново.

    Список доступных моделей вынесен в общий модуль model_catalog.py, поэтому оба инструмента работают с одним и тем же набором. Команды чата тоже общие: /help, /clear, /exit. Исходный код и инструкции лежат в репозитории:

    https://github.com/zefir1990/AirLLM-experiments

    На что обратить внимание

    Первый ответ может готовиться несколько минут: сначала AirLLM скачивает модель и режет её на слои, и только потом начинает генерацию. Дальше скорость, впрочем, тоже остаётся невысокой — это осознанный компромисс ради экономии памяти. Для gated-моделей вроде meta-llama нужно принять условия на Hugging Face и передать токен доступа. А кеш скачанных моделей и шардов лучше не удалять, иначе всё придётся загружать заново.

    Ссылки

    https://github.com/lyogavin/airllm
    https://github.com/mkamranr/airllm-openai-server
    https://github.com/zefir1990/AirLLM-experiments
    https://pypi.org/project/airllm/
    https://huggingface.co/

    Источники

    https://github.com/lyogavin/airllm
    https://github.com/zefir1990/AirLLM-experiments

  • Установка OpenCode и подключение DeepSeek

    OpenCode — это открытый агент для программирования, который живёт прямо в терминале. Он умеет читать файлы проекта, править код, запускать команды и тесты, искать по репозиторию и работать с git — примерно то же, что делают Claude Code и другие консольные агенты. Главное отличие в том, что OpenCode не привязан к одному поставщику моделей: через него можно подключить десятки провайдеров, в том числе DeepSeek. В этой заметке я опишу установку агента и подключение к нему DeepSeek.

    Что понадобится

    Для работы нужны всего две вещи:

    • Современный терминал. Подойдёт WezTerm, Alacritty, Ghostty, Kitty или любой другой.
    • API-ключ провайдера. В нашем случае — DeepSeek.

    Node.js не обязателен, если ставить агент штатным установочным скриптом, — binary приезжает готовым.

    Установка

    Самый простой способ — официальный установочный скрипт:

    curl -fsSL https://opencode.ai/install | bash
    

    Он сам определит платформу и положит исполняемый файл в PATH. После установки стоит проверить, что всё на месте:

    opencode --version
    

    Если команда отображает версию, установка прошла успешно.

    Установка другими способами

    Если вы предпочитаете пакетные менеджеры, вариантов несколько. Через Node.js:

    npm install -g opencode-ai
    

    То же самое умеют Bun, pnpm и Yarn:

    bun install -g opencode-ai
    pnpm install -g opencode-ai
    yarn global add opencode-ai
    

    В macOS и Linux есть Homebrew. Обратите внимание: рекомендуют именно tap от разработчиков, а не официальную формулу — она обновляется реже:

    brew install anomalyco/tap/opencode
    

    В Arch Linux:

    sudo pacman -S opencode
    paru -S opencode-bin
    

    Первая команда ставит стабильную версию из репозитория, вторая — свежую из AUR.

    Установка в Windows

    В Windows рекомендуют работать через WSL: так и производительность выше, и совместимость с возможностями агента полная. Но есть и нативные способы. Через Chocolatey, Scoop или npm:

    choco install opencode
    scoop install opencode
    npm install -g opencode-ai
    

    Есть и вариант через Mise, а также контейнер Docker:

    mise use -g github:anomalyco/opencode
    docker run -it --rm ghcr.io/anomalyco/opencode
    

    Наконец, готовый бинарник всегда можно взять со страницы релизов на GitHub.

    Ключ DeepSeek

    Ключ создаётся в личном кабинете DeepSeek. Заходим на platform.deepseek.com, открываем раздел с API-ключами и нажимаем создание нового ключа. Полученную строку лучше сразу сохранить: показана она будет только один раз.

    Подключение DeepSeek

    Дальше всё делается внутри самого агента. Запускаем OpenCode в терминале:

    opencode
    

    В интерфейсе выполняем команду подключения:

    /connect
    

    В открывшемся списке провайдеров ищем DeepSeek, выбираем его и вставляем API-ключ. Ключи, добавленные таким образом, сохраняются в файл:

    ~/.local/share/opencode/auth.json
    

    После подключения остаётся выбрать модель командой:

    /models
    

    В списке будут доступны модели DeepSeek, в том числе deepseek-v4-pro, deepseek-flash и deepseek-v4-flash. Для рутинной работы в репозитории вполне хватает flash-варианта; на сложные задачи имеет смысл переключаться на pro.

    Настройка через конфиг

    Если не хочется хранить ключ через мастер или нужно задать свой адрес API, провайдер описывается прямо в конфигурационном файле opencode.json:

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "deepseek": {
          "options": {
            "baseURL": "https://api.deepseek.com"
          }
        }
      }
    }
    

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

    export DEEPSEEK_API_KEY=ваш_ключ_deepseek
    opencode
    

    Такой вариант хорош для CI и одноразовых запусков: ключ не оседает в хранилище, а живёт только в окружении процесса.

    Первый запуск в проекте

    Переходим в каталог проекта и запускаем агента:

    cd ваш_проект
    opencode
    

    Первым делом полезно инициализировать его:

    /init
    

    OpenCode проанализирует структуру проекта и создаст файл AGENTS.md — аналог инструкций для агента. Этот файл стоит закоммитить в git: он помогает агенту понимать принятые в проекте соглашения и паттерны.

    Как этим пользоваться

    Работа строится примерно так же, как с другими агентами. Пара приёмов, которые стоит знать сразу:

    • Режим планирования. Клавиша Tab переключает агента между режимами планирования и сборки. В первом он только предлагает, как решить задачу, и ничего не меняет, — удобно проверить замысел до правок.
    • Ссылки на файлы. Клавиша @ открывает нечёткий поиск по файлам проекта, чтобы передать агенту конкретный файл в контекст.
    • Откат изменений. Команда /undo отменяет последние правки и возвращает исходный запрос, /redo — возвращает их обратно. Откатывать можно несколько шагов подряд.
    • Обмен диалогом. Команда /share создаёт ссылку на текущую беседу и копирует её в буфер обмена. По умолчанию диалоги никуда не публикуются.

    Для простых изменений можно и не переходить в режим планирования, а сразу описать, что нужно сделать, и указать файлы через @.

    На что обратить внимание

    Пара практических моментов. OpenCode не требует подписки: вы платите провайдеру напрямую по факту расхода токенов, поэтому длинные сессии с DeepSeek предсказуемо дешевле топовых моделей. Ключ хранится локально в auth.json — это стоит учитывать на общей машине, а для чужих окружений надёжнее переменная окружения.

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

    Ссылки

    https://opencode.ai/
    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/
    https://github.com/anomalyco/opencode
    https://platform.deepseek.com/api_keys
    https://api-docs.deepseek.com

    Источники

    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/#deepseek
    https://github.com/anomalyco/opencode/releases
    https://models.dev/

  • Установка Asahi Linux на MacBook с процессором M2

    На Mac с процессором Apple Silicon нельзя поставить Linux привычным способом: загрузка с флешки здесь невозможна, а загрузчик подписан Apple. Обойти это взялся проект Asahi Linux — его участники реверс-инжинирингом заставили железо M1 и M2 работать под обычным Linux. Сейчас флагманский дистрибутив проекта — Fedora Asahi Remix, и на M2 он работает вполне уверенно. В этой заметке я опишу установку шаг за шагом.

    Что такое Asahi Linux

    Проект стартовал в 2020 году, когда Apple перешла на собственные чипы и вместе с ними пришла закрытая схема загрузки: устройство запускает только подписанный Apple код. Участники Asahi Linux написали свой загрузчик m1n1, поверх него — U-Boot и UEFI-окружение. Дальше загружается самая обычная ARM64-система, но с ядром linux-asahi, в которое добавлены драйверы для нестандартного железа Apple: GPU, блоков видео, звука, камеры.

    Практически это выглядит так: вы запускаете установщик из macOS, он размечает диск, ставит загрузочную цепочку и систему. macOS остаётся на месте, вы получаете двойную загрузку и выбираете систему при включении.

    Требования

    • Mac на чипе M2 — MacBook Air, MacBook Pro 13″, а также 14″ и 16″ с M2 Pro и M2 Max. Поддерживаются и машины на M1.
    • Актуальная macOS. Установщик требует свежую версию системы, так что перед началом обновитесь.
    • Свободное место. Минимум около 30 ГБ, комфортно — 100 ГБ и больше. Место отрезается от контейнера APFS и обратно само не вернётся.
    • Пароль администратора macOS. Он понадобится и при разметке, и при первой загрузке в режиме восстановления.
    • Резервная копия. Разметка диска — операция, которую лучше начинать со свежим бэкапом.

    Шаг 1. Резервная копия и обновление

    Сделайте копию через Time Machine или любым другим способом. Обновите macOS до последней доступной версии и перезагрузитесь. Если включён FileVault, пароль от него всё равно понадобится на этапе загрузки в режиме восстановления — установщику нужно разблокировать диск.

    Шаг 2. Запуск установщика

    Откройте Terminal в macOS и выполните одну команду:

    curl https://alx.sh | sh
    

    Это официальный установщик проекта. Он скачает себя, проверит модель Mac и предложит выбрать дистрибутив. Есть и вариант сразу для Fedora:

    curl https://fedora-asahi-remix.org/install | sh
    

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

    Шаг 3. Разметка диска

    Установщик попросит пароль администратора и покажет текущую разметку. Дальше диалог выглядит так:

    • Нажмите r, чтобы изменить размер раздела macOS.
    • Введите размер нового Linux-раздела. Можно в гигабайтах, можно в процентах от диска, а можно написать min — тогда отрежется минимально возможный кусок.
    • Подтвердите разметку буквой y. Этот шаг самый долгий: система физически перемещает данные в контейнере APFS, на большом диске это занимает несколько минут.
    • Нажмите f, чтобы указать установщику на только что созданный свободный раздел.

    Важно понимать, что происходит: Linux ставится не внутрь APFS, а на отдельный раздел рядом с ним. Именно поэтому macOS остаётся нетронутой, а откат сводится к удалению этого раздела.

    Шаг 4. Выбор дистрибутива и рабочего стола

    Дальше установщик предложит систему и окружение рабочего стола. Fedora Asahi Remix идёт с KDE Plasma по умолчанию — это флагманский вариант, который получает обновления первым. Альтернатива — GNOME, он ближе по ощущениям к macOS. Есть и минимальный образ, если графическое окружение вам не нужно.

    Шаг 5. Первая загрузка в режиме восстановления

    После разметки установщик предложит продолжить, и Mac выключится. Здесь начинается самая непривычная часть:

    • Подождите не меньше 25 секунд — это требование Apple к переходу в режим восстановления.
    • Зажмите кнопку питания и держите, пока не появятся варианты загрузки.
    • Выберите установочный том и введите пароль macOS.

    На этом шаге в служебный раздел устанавливается загрузочная цепочка: m1n1, U-Boot и UEFI-окружение. Затем машина перезагрузится ещё раз — снова зажмите кнопку питания и выберите новый том, теперь уже чтобы попасть в систему. Таких перезагрузок будет несколько, каждая требует ручного выбора: по умолчанию Mac продолжает грузить macOS.

    Шаг 6. Установка системы

    Дальше запускается мастер первоначальной настройки: имя системы, пользователь, пароль, часовой пояс, раскладка клавиатуры. После этого начинается собственно установка, и она уже идёт из интернета — понадобится стабильное соединение, а по времени это занимает от пятнадцати минут до получаса.

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

    Что работает, а что нет

    Перед установкой стоит трезво оценить, чего вы лишитесь. На M2-ноутбуках положение такое:

    Работает:

    • Wi-Fi и Bluetooth — без ограничений.
    • Встроенный экран и графика — с аппаратным ускорением, в том числе OpenGL и Vulkan.
    • Веб-камера, микрофон и динамики на MacBook Air и MacBook Pro.
    • Спящий режим — работает штатно.
    • Аппаратное декодирование видео — плееры не нагружают процессор.
    • USB-порты, в том числе USB 2 и USB 3 через разъёмы Thunderbolt.

    Не работает или работает частично:

    • Touch ID. Отпечаток пальца в Linux недоступен — входите по паролю.
    • Thunderbolt (USB4). Статус — в разработке. Устройства, которым нужен именно Thunderbolt, к сожалению, не подойдут.
    • Внешний монитор по USB-C. Режим DisplayPort Alt Mode тоже пока в работе. На ноутбуках M2 порта HDMI нет, так что внешний экран подключить не получится. На моделях с M2 Pro и M2 Max HDMI есть, и он работает. Обходной путь для USB-C — в разделе про ядро fairydust ниже.
    • Аппаратное кодирование видео — в планах, декодирование при этом уже есть.

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

    Внешний монитор через ядро fairydust

    Вывод изображения по USB-C есть в экспериментальной ветке ядра fairydust от команды Asahi Linux. Собирать её вручную долго, поэтому проще воспользоваться скриптом-обёрткой из проекта asahi-fairydust-display:

    git clone https://github.com/bharambetejas/asahi-fairydust-display
    cd asahi-fairydust-display
    chmod +x asahi-fairydust-build.sh
    ./asahi-fairydust-build.sh
    

    Скрипт клонирует ветку fairydust, настраивает и собирает ядро, ставит его рядом со штатным и правит GRUB. Нужно 15 ГБ свободного места, USB-C → HDMI или DisplayPort адаптер и 60–90 минут на сборку. После перезагрузки выбираем в GRUB ядро с меткой -fairydust и вставляем адаптер в самый передний порт USB-C.

    Проверка после загрузки:

    uname -r
    glxinfo | grep "OpenGL renderer"
    xrandr
    

    Первая команда должна показать версию с суффиксом -fairydust, вторая — Apple M2, а не llvmpipe, третья — подключённый выход DP-1.

    Оговорки. Ветка экспериментальная и официально не поддерживается. Вывод работает только через один порт USB-C и не всегда переживает горячее подключение — надёжнее перезагрузиться с уже вставленным адаптером. Автоматическую настройку дисплея скрипт не делает: соответствующий шаг в нём отключён, потому что вызывал цикл входа на Wayland. Обновление штатного ядра через dnf переставляет симлинк /boot/dtb, из-за чего вывод может тихо перестать работать — тогда сборку надо повторить.

    Штатное ядро при этом не трогается: чтобы вернуться, достаточно выбрать его в меню GRUB.

    Внешний диск вместо апгрейда

    А вот приятная сторона. MacBook невозможно апгрейдить: память и накопитель распаяны на плате, и их объём выбирается один раз при покупке. В macOS внешний диск так и остаётся внешним — систему и приложения на него толком не перенести.

    В Linux это ограничение снимается: внешний SSD по USB — обычное блочное устройство, и любую точку монтирования можно увести на него через /etc/fstab. Хотите перенести домашний каталог, библиотеку Steam, образы виртуальных машин или локальные модели — пожалуйста. Получается ровно тот апгрейд, которого у этой машины не бывает.

    Выглядит это так. Сначала смотрим UUID нужного раздела:

    lsblk -f
    sudo blkid
    

    Затем добавляем строку в /etc/fstab:

    UUID=ваш-uuid  /home  ext4  defaults,nofail  0  2
    

    Сам перенос делают копированием: данные переезжают на внешний диск, после чего он монтируется в нужную точку. Чаще так уводят не весь /home, а отдельные тяжёлые каталоги — так меньше риска.

    Пара оговорок. Параметр nofail в опциях ставить стоит: без него система не загрузится, если диск не подключён. Скорость USB-накопителя всё же ниже встроенного NVMe, так что под частые операции ввода-вывода место на внутреннем диске лучше оставлять. И выдёргивать диск на ходу с примонтированными туда каталогами не нужно — сначала размонтируйте.

    Как переключаться между системами

    При включении зажмите кнопку питания — появится меню загрузки, в нём будут macOS и Linux. Выбрать систему по умолчанию можно в macOS: «Системные настройки» → «Основные» → «Системный диск». В Linux соответствующие настройки тоже есть, так что переключаться в обе стороны можно без терминала.

    Ссылки

    https://asahilinux.org/fedora/
    https://asahilinux.org/docs/
    https://asahilinux.org/docs/platform/feature-support/m2/
    https://fedoraproject.org/asahi-remix
    https://github.com/bharambetejas/asahi-fairydust-display
    https://github.com/AsahiLinux/linux/tree/fairydust

    Источники

    https://asahilinux.org/docs/platform/feature-support/m2/
    https://discussion.fedoraproject.org/t/fedora-asahi-remix-installation-guide/
    https://github.com/AsahiLinux/docs
    https://github.com/bharambetejas/asahi-fairydust-display/blob/main/README.md
    https://github.com/bharambetejas/asahi-fairydust-display/blob/main/asahi-fairydust-build.sh

  • Установка VirtualBox с гостевыми дополнениями на Mac с процессором ARM

    Инструкция по установке Ubuntu Server в VirtualBox на Mac с процессором ARM64 и подключению гостевых дополнений.

    Установка VirtualBox

    Скачиваем ARM64-сборку VirtualBox, монтируем образ и переносим VirtualBox.app в Applications.

    Настройка виртуальной машины

    Создаём новую машину, указываем тип Linux и версию Ubuntu (64-bit ARM). В настройках задаём:

    • TPM — отключить
    • UEFI — отключить
    • Оперативная память — 4096 МБ.
    • Контроллер графики — VMSVGA (для VBoxLinuxAdditions).
    • Процессор — 4 ядра.

    Установка Ubuntu Server

    Монтируем ISO-образ Ubuntu Server для ARM64 в привод CD-ROM. В разделе загрузки ставим оптический привод первым в списке. Запускаем машину и проходим установку Ubuntu Server.

    Установка xubuntu-desktop

    Устанавливаем графическое окружение XFCE:

    sudo apt update
    sudo apt install -y xubuntu-desktop
    

    В диалоге выбора менеджера входа выбираем lightdm.

    Перезагружаем гостевую систему:

    sudo reboot
    

    Гостевые дополнения

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

    sudo apt update
    sudo apt install -y build-essential linux-headers-$(uname -r)
    

    Монтируем диск с гостевыми дополнениями через меню VirtualBox: Устройства → Подключить образ диска Дополнений гостевой ОС. Диск появится внутри гостя в каталоге /media/$USER.

    Открываем терминал в Xubuntu, переходим в каталог смонтированного диска и запускаем установщик:

    cd /media/$USER/*
    sudo ./VBoxLinuxAdditions-arm64.run
    

    Перезагружаем гостевую систему:

    sudo reboot
    

    Что должно работать после установки

    Проверяем, что модули ядра загружены:

    lsmod | grep vbox
    

    В выводе должны быть vboxguest и vboxsf.

    Добавляем общую папку в свойствах машины и монтируем её внутри гостя:

    sudo mkdir -p /media/sf_shared
    sudo mount -t vboxsf shared /media/sf_shared
    

    Работающие возможности:

    • Общие папки — каталог хоста виден внутри гостя через vboxsf.
    • Общий буфер обмена — копирование текста между хостом и гостем.
    • Интеграция мыши — курсор свободно выходит за пределы окна машины.
    • Автоматическое изменение разрешения — экран гостя подстраивается под размер окна.
    • Синхронизация времени — время гостя приводится к времени хоста.

    Источники

    https://www.virtualbox.org/manual/topics/guestadditions.html
    https://forums.virtualbox.org/viewtopic.php?t=112886

  • Как подключить DeepSeek к Claude Code

    Claude Code — это консольный агент от Anthropic, который умеет читать файлы проекта, править код, запускать команды и тесты, искать по репозиторию и работать с git. Главное неудобство при его использовании — стоимость: длинные сессии с большим контекстом быстро съедают бюджет подписки.

    Решение есть. DeepSeek предоставляет endpoint, совместимый с Anthropic API. Достаточно поменять базовый адрес и токен, и Claude Code начнёт работать на моделях DeepSeek, не требуя ни переустановки, ни патчей. В этой заметке я опишу, как это настроить на macOS, Linux и Windows.

    Зачем это нужно

    Причины могут быть разные:

    — Цена. Модели DeepSeek стоят заметно дешевле Claude, а для рутинных задач агента — навигации по проекту, мелких правок, запуска тестов — топовое качество нужно не всегда.

    — Доступность. Если у вас нет подписки или оплата картой Anthropic по каким-то причинам недоступна, DeepSeek становится рабочим вариантом.

    — Эксперименты. Интересно сравнить, как разные модели справляются с одной и той же кодовой базой в одинаковой среде.

    Что понадобится

    Вам нужен установленный Claude Code и API-ключ DeepSeek. Если Claude Code ещё не стоит, порядок такой:

    1. Установите Node.js 18 или новее. В Windows дополнительно понадобится Git for Windows.

    2. Установите сам Claude Code командой ниже.

    3. Проверьте установку.

    npm install -g @anthropic-ai/claude-code
    claude --version
    

    Если версия отображается, значит установка прошла успешно. API-ключ создаётся в личном кабинете DeepSeek на странице ключей.

    Настройка через переменные окружения

    Вся интеграция сводится к переменным окружения. Для macOS и Linux это выглядит так:

    export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
    export ANTHROPIC_AUTH_TOKEN=ваш_ключ_deepseek
    
    export ANTHROPIC_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-flash
    export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-flash
    
    export CLAUDE_CODE_EFFORT_LEVEL=max
    export CLAUDE_CODE_AUTO_COMPACT_WINDOW=786432
    

    Для Windows в PowerShell синтаксис другой, но имена переменных те же:

    $env:ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
    $env:ANTHROPIC_AUTH_TOKEN="ваш_ключ_deepseek"
    $env:ANTHROPIC_MODEL="deepseek-flash[1m]"
    

    Разберём, что здесь к чему. Переменная `ANTHROPIC_BASE_URL` перенаправляет запросы с серверов Anthropic на DeepSeek. `ANTHROPIC_AUTH_TOKEN` подставляет ваш ключ. Остальные переменные задают, какая модель используется в той или иной роли. Есть модели с суффиксом `[1m]` — это вариант с окном контекста около миллиона токенов, что позволяет агенту за один раз удерживать значительно больше файлов проекта. Соответственно `CLAUDE_CODE_AUTO_COMPACT_WINDOW` выставляется под размер этого окна, чтобы автоматическое сжатие истории не срабатывало слишком рано.

    Переменные удобно не экспортировать каждый раз вручную, а прописать в конфигурации Claude Code. Тогда настройки будут подхватываться автоматически при запуске.

    {
      "env": {
        "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
        "ANTHROPIC_AUTH_TOKEN": "ваш_ключ_deepseek",
        "ANTHROPIC_MODEL": "deepseek-flash[1m]",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-flash",
        "CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-flash"
      }
    }
    

    После этого переходите в каталог проекта и запускайте агент:

    cd ваш_проект
    claude
    

    Claude Code в VS Code

    Claude Code доступен не только в терминале, но и как расширение для VS Code. Устанавливается оно штатным образом: откройте панель расширений по Cmd+Shift+X, найдите «Claude Code» и нажмите Install. Расширение выпускает сам Anthropic.

    Внутри расширения лежит собственная копия CLI, и читает оно тот же файл ~/.claude/settings.json, что и терминальная версия. Настройки из предыдущего раздела действуют и здесь — но с одной оговоркой, из-за которой чаще всего и возникает путаница.

    Проверка входа происходит до запуска

    Перед стартом расширение проверяет учётные данные из своей собственной настройки `claudeCode.environmentVariables`, а не из settings.json. Значения из settings.json доходят до запущенного процесса — то есть адрес API и выбранные модели подхватываются корректно, — но собственную проверку входа расширения они не проходят. Если вы видите экран входа при том, что в терминале всё уже работает, причина именно в этом.

    Лечится это несколькими строками в настройках VS Code: продублировать переменные в `claudeCode.environmentVariables` и отключить запрос входа.

    {
      "claudeCode.environmentVariables": [
        { "name": "ANTHROPIC_BASE_URL", "value": "https://api.deepseek.com/anthropic" },
        { "name": "ANTHROPIC_AUTH_TOKEN", "value": "ваш_ключ_deepseek" },
        { "name": "ANTHROPIC_MODEL", "value": "deepseek-flash[1m]" }
      ],
      "claudeCode.disableLoginPrompt": true
    }
    

    Нюанс macOS

    Если запускать VS Code из Dock или Finder, он не наследует переменные окружения из ~/.zshrc. Это стандартное поведение macOS, и оно отдельно оговорено в документации Claude Code. То есть `export ANTHROPIC_BASE_URL=…` в вашем шелле для расширения просто не сработает — этой переменной в его окружении не окажется.

    Отсюда два вывода. Во-первых, настройки в `claudeCode.environmentVariables` надёжнее, чем переменные шелла. Во-вторых, если вы всё же предпочитаете шелл, запускайте редактор из терминала, где переменные уже экспортированы:

    code .
    

    Что не будет работать

    На стороннем провайдере часть возможностей расширения недоступна, потому что они требуют аккаунта claude.ai:

    — не будет полосы использования плана, голосового ввода и вкладки Web для облачных сессий;

    — команды выхода из аккаунта в меню не показываются;

    — `/usage` вместо лимитов плана покажет расход и количество токенов текущей сессии;

    — Remote Control не работает, если базовый адрес указывает не на Anthropic.

    Это ожидаемое поведение, а не признак того, что настройка не удалась.

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

    Как DeepSeek понимает имена моделей Claude

    Claude Code внутри обращается к моделям по именам вида claude-opus, claude-sonnet и claude-haiku. DeepSeek перехватывает эти имена и подменяет их на свои:

    — всё, что начинается с claude-opus, уходит на deepseek-v4-pro;

    — всё, что начинается с claude-sonnet или claude-haiku, уходит на deepseek-flash;

    — неизвестное имя модели тоже приводится к deepseek-flash.

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

    На что обратить внимание

    Совместимость неполная, и об этом стоит знать заранее. Часть возможностей Anthropic API DeepSeek просто игнорирует:

    — Кэширование промптов. Поле `cache_control` не поддерживается. Claude Code активно пользуется кэшем, чтобы не переплачивать за повторную отправку одного и того же контекста. Здесь этого не будет, поэтому длинные сессии обходятся дороже, чем можно ожидать по прайсу за токен. Периодически начинайте новый диалог вместо бесконечного продолжения старого.

    — Бюджет размышлений. Параметр `thinking` поддерживается, но `budget_tokens` внутри него игнорируется.

    — Разное. Поля `top_k`, `service_tier`, `container` и подключение MCP-серверов со стороны API также игнорируются.

    — MCP. Встроенный механизм MCP-серверов на стороне DeepSeek не работает, хотя обычные инструменты агента — чтение файлов, правка, запуск команд, поиск — функционируют нормально.

    Отдельно стоит упомянуть про прокси. В сообществе есть проекты вроде `ds-cc-proxy`, которые ставятся между Claude Code и DeepSeek и берут на себя выравнивание мелких несовместимостей, а также разводят основную сессию и субагентов по разным моделям. Если стандартная настройка ведёт себя нестабильно, такой промежуточный слой может помочь.

    Claude Code Router

    Способ, описанный выше, ставит одну модель на все задачи. Но агент занимается очень разными вещами: разбирается в структуре проекта, правит мелочи, а иногда решает и по-настоящему сложную задачу. Логично, чтобы на разные типы работы шли разные модели. Ровно это и умеет claude-code-router (CCR) — локальный шлюз, который встаёт между Claude Code и провайдерами моделей.

    Что это даёт

    — Маршрутизацию по правилам. Можно задать, какая модель обслуживает основную сессию, какая — фоновые задачи, какая — режим планирования. Дорогую модель имеет смысл ставить только на сложные рассуждения, а рутину отдавать дешёвой.

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

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

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

    Сначала о версиях — это важно

    Здесь стоит предупредить, потому что сэкономит вам время. CCR за свою историю сильно переделали, и почти все статьи, которые вы найдёте в поиске, описывают устаревший вариант.

    В старых версиях конфигурация лежала в файле ~/.claude-code-router/config.json с блоками Providers и Router, а запускалось всё командой `ccr code`. Сейчас так уже не работает. Актуальная версия (3.x) хранит настройки в базе SQLite и управляется через веб-интерфейс. Старый config.json читается ровно один раз как источник для миграции, если базы ещё нет, — после этого правки в нём ни на что не влияют.

    То есть если вы нашли в интернете инструкцию с редактированием config.json и командой `ccr code` и у вас ничего не заработало — вы правите файл, который уже никто не читает. Это не ваша ошибка.

    Установка

    Понадобится Node.js 22 или новее.

    npm install -g @musistudio/claude-code-router
    ccr ui
    

    Команда `ccr ui` поднимает службу в фоне и открывает в браузере интерфейс управления. Полезно знать и остальные команды: `ccr start` запускает службу, `ccr stop` останавливает её, `ccr serve` работает на переднем плане и удобен, когда нужно видеть логи.

    По умолчанию интерфейс управления живёт на порту 3458, а сам шлюз для моделей — на порту 3456.

    Настройка

    Всё делается в веб-интерфейсе, без ручного редактирования файлов:

    1. В разделе Providers добавьте провайдера DeepSeek и укажите его ключ. Обратите внимание: здесь нужен полный адрес до точки chat/completions, а не просто домен.

    2. В разделе Models опишите, что это за модель, — описание помогает маршрутизации.

    3. В разделе Agent Config задайте модель по умолчанию.

    4. В разделе Routing настройте правила: какая модель на какие запросы идёт.

    5. На странице API Keys создайте ключ клиента CCR — именно его, а не ключ DeepSeek, будет использовать Claude Code.

    После этого остаётся направить Claude Code на шлюз: в качестве базового адреса укажите адрес шлюза, показанный в интерфейсе, а в качестве токена — ключ клиента CCR.

    Приятный момент: правила маршрутизации можно писать не только полями, но и скриптом на JavaScript, если логика сложнее, чем сравнение одного поля.

    Стоит ли этим пользоваться

    Подключение DeepSeek к Claude Code — это в первую очередь способ снизить расходы, не отказываясь от удобной агентной среды. Вы получаете тот же интерфейс, те же инструменты и тот же рабочий процесс, но на другой модели. Настройка занимает несколько минут и полностью обратима: достаточно убрать переменные окружения, чтобы вернуться к моделям Anthropic.

    Ограничения тоже понятны: нет кэша промптов, неполная совместимость по части полей API, а качество модели на сложных архитектурных задачах может отличаться. Для рутинной работы в репозитории — навигации, рефакторинга, прогона тестов — этого более чем достаточно.

    Пример: Oni-Extended

    Живой пример такой связки — мой проект Oni-Extended, форк порта игры Oni (Bungie, 2001) под Apple Silicon. Исходники игры написаны на C и живут больше двадцати лет, тестов в них никогда не было, а размеры файлов измеряются десятками тысяч строк. Тем не менее через Claude Code с DeepSeek в проект были добавлены новые функции: быстрое сохранение и загрузка по F5/F9, блок удержанием клавиши и флаг запуска -nodamage, отключающий урон.

    Это хорошая иллюстрация того, о чём говорилось выше. Все задачи относятся к рутинной работе в большом репозитории — агент исследует чужой код, находит связи между модулями и проверяет гипотезы, а не проектирует архитектуру с нуля. Стоимость таких сессий на DeepSeek оказывается заметно ниже, чем на моделях Anthropic, при том что результат проверяется собственной инфраструктурой проекта: сборкой, стендом прогона уровней и автономными тестами.

    Ссылки

    https://platform.deepseek.com/api_keys
    https://api-docs.deepseek.com
    https://docs.anthropic.com/en/docs/claude-code
    https://github.com/anthropics/claude-code
    https://github.com/musistudio/claude-code-router
    https://ccrdesk.top/en/guides/cli/
    https://github.com/zefir1990/Oni-Extended

    Источники

    https://api-docs.deepseek.com/guides/anthropic_api
    https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code
    https://ccrdesk.top/en/routing/
    https://code.claude.com/docs/en/vs-code

  • Donki Hills has officially been on Steam for over a year!

    Donki Hills уже больше года в Steam!

    Ровно год назад проект появился на странице Steam, и с тех пор игра прошла большой путь. За это время было много экспериментов, изменений и поисков того самого направления, в котором Donki Hills должна развиваться.

    Работа над геймплеем продолжается, и сейчас проект постепенно приобретает своё настоящее лицо. Donki Hills начинает превращаться в то, чем она задумывалась — Comedy First Person Roguelike Parody Survival Horror.

    Странное приключение, где хоррор встречается с юмором, исследованием и неожиданными ситуациями. Вам предстоит отправиться в город, искать предметы, разгадывать загадки и постепенно раскрывать тайну, которая приведёт вас к Марии.

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

    Очень интересно услышать вашу реакцию! 👀
    Как вам новое направление? Какие впечатления от атмосферы и идеи игры?

    Спасибо всем, кто добавил Donki Hills в список желаемого, следит за разработкой и поддерживает проект ❤️

    Впереди ещё много экспериментов, новых механик и странных приключений.

    https://store.steampowered.com/app/3476390/Donki_Hills/

  • Обходим троттлинг на процессорах игровых лаптопов

    Производители современных игровых ноутбуков часто настраивают системы так, что процессор под нагрузкой прогревается до 90 градусов и даже доходит до 100 градусов Цельсия. Особенно остро эта проблема проявляется в летний период при запуске требовательных игр или тяжелых рабочих задач.

    Из-за экстремального нагрева активируется троттлинг (сброс частот процессора для предотвращения его физического повреждения), что приводит к резкой потере производительности, фризам и падению FPS. Существует немало способов борьбы с перегревом (от использования охлаждающих подставок до андервольтинга), однако в этой заметке я опишу самый простой и быстрый метод, актуальный для Windows 11 и более ранних версий операционной системы.

    Метод заключается в ограничении максимального состояния процессора через настройки схемы электропитания:

    1. Откройте настройки электропитания через поиск Windows или панель управления.
    2. В активной схеме питания перейдите в раздел изменения дополнительных параметров.
    3. Найдите ветку Processor power management (Управление питанием процессора).
    4. Раскройте параметр Maximum processor state (Максимальное состояние процессора).
    5. Для пункта Plugged In (От сети) измените значение со 100% на 80% или меньше.

    После применения этих настроек процессор перестанет автоматически разгоняться до предельных частот, выделяющих избыточное тепло. Несмотря на то, что максимальная пиковая производительность снизится примерно на 20%, процессор перестанет перегреваться. Стабильная работа на сниженной частоте без троттлинга ГОРАЗДО лучше и комфортнее для системы, чем постоянные резкие скачки производительности из-за перегрева при температурах под 100 градусов.

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

  • Портирование Surreal Engine C++ на WebAssembly

    В этой заметке я опишу то как я портировал игровой движок Surreal Engine на WebAssembly.
    https://demensdeum.com/demos/SurrealEngine/»
    Surreal Engine – игровой движок который реализует большую часть функционала движка Unreal Engine 1, известные игры на этом движке – Unreal Tournament 99, Unreal, Deus Ex, Undying. Он относится к классическим движкам, которые работали преимущественно в однопоточной среде выполнения.
    Изначально у меня была идея взяться за проект который я не смогу выполнить в какой-либо разумный срок, таким образом показав своим подписчикам на Twitch, что есть проекты которые не могу сделать даже я. В первый же стрим я внезапно понял что задача портирования Surreal Engine C++ на WebAssembly с помощью Emscripten выполнима.

    Спустя месяц я могу продемонстрировать свой форк и сборку движка на WebAssembly:
    https://demensdeum.com/demos/SurrealEngine/
    Управление как и в оригинале, осуществляется на стрелках клавиатуры. Далее планирую адаптацию под мобильное управление (тачи), добавление корректного освещения и прочие графические фишки рендера Unreal Tournament 99.

    С чего начать?

    Первое о чем хочется сказать, это то что любой проект можно портировать с C++ на WebAssembly с помощью Emscripten, вопрос лишь в том насколько полным получится функционал. Выбирайте проект порты библиотек которого уже доступны для Emscripten, в случае Surreal Engine очень сильно повезло, т.к. движок использует библиотеки SDL 2, OpenAL – они обе портированы под Emscripten. Однако в качестве графического API используется Vulkan, который на данный момент не доступен для HTML5, ведутся работы по реализации WebGPU, но он также находится в стадии черновика, также неизвестно насколько простым будет дальнейший порт из Vulkan на WebGPU, после полной стандартизации оного. Поэтому пришлось написать свой собственный базовый OpenGL-ES / WebGL рендер для Surreal Engine.

    Сборка проекта

    Система сборки в Surreal Engine – CMake, что тоже упрощает портирование, т.к. Emscripten предоставляет свои нативные сборщики – emcmake, emmake.
    За основу порта Surreal Engine брался код моей последней игры на WebGL/OpenGL ES и C++ под названием Death-Mask, из-за этого разработка шла гораздо проще, все необходимые флаги сборки были с собой, примеры кода.
    Один из важнейших моментов в CMakeLists.txt это флаги сборки для Emscripten, ниже пример из файла проекта:

    set(CMAKE_CXX_FLAGS "-s MIN_WEBGL_VERSION=2 
    -s MAX_WEBGL_VERSION=2 
    -s EXCEPTION_DEBUG 
    -fexceptions 
    --preload-file UnrealTournament/ 
    --preload-file SurrealEngine.pk3 
    --bind 
    --use-preload-plugins 
    -Wall 
    -Wextra 
    -Werror=return-type 
    -s USE_SDL=2 
    -s ASSERTIONS=1 
    -w 
    -g4 
    -s DISABLE_EXCEPTION_CATCHING=0 
    -O3 
    --no-heap-copy 
    -s ALLOW_MEMORY_GROWTH=1 
    -s EXIT_RUNTIME=1")
    

    Сам сборочный скрипт:

    clear
    emmake make -j 16
    cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data
    cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js
    cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm
    cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html
    

    Далее подготовим index.html, который включает в себя прелоадер файловой системы проекта. Для выкладывания в веб я использовал Unreal Tournament Demo версии 338. Как можно увидеть из файла CMake, распакованная папка игры была добавлена с сборочную директорию и прилинкована как preload-file для Emscripten.

    Основные изменения кода

    Затем предстояло поменять игровой цикл игры, запускать бесконечный цикл нельзя, это приводит к зависанию браузера, вместо этого нужно использовать emscripten_set_main_loop, об этой особенности я писал в своей заметке 2017 года “Портирование SDL C++ игры на HTML5 (Emscripten)”
    Код условия выхода из цикла while меняем на if, далее выводим основной класс игрового движка, который содержит игровой луп, в глобальный скоп, и пишем глобальную функцию которая будет вызывать шаг игрового цикла из глобального объекта:

    #if __EMSCRIPTEN__
    #include <emscripten.h>
    Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
    void emscripten_game_loop_step() {
    	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
    }
    #endif
    

    После этого нужно убедиться что в приложении отсутствуют фоновые потоки, если они есть, то приготовьтесь к переписываю их на однопоточное выполнение, либо использование библиотеки phtread в Emscripten.
    Фоновый поток в Surreal Engine используется для проигрывания музыки, из главного потока движка приходят данные о текущем треке, о необходимости проигрывания музыки, либо ее отсутствии, затем фоновый поток по мьютексу получает новое состояние и начинает проигрывать новую музыку, либо приостанавливает. Фоновый поток также используется для буферизации музыки во время проигрывания.
    Мои попытки собрать Surreal Engine под Emscripten с pthread не увенчались успехом, по той причине что порты SDL2 и OpenAL были собраны без поддержки pthread, а их пересборкой ради музыки я заниматься не хотел. Поэтому перенес функционал фонового потока музыки в однопоточное выполнение с помощью цикла. Удалив вызовы pthread из C++ кода, я перенес буферизацию, проигрывание музыки в основной поток, чтобы не было задержек я увеличил буфер на несколько секунд.
    Далее я опишу уже конкретные реализации графики и звука.

    Vulkan не поддерживается!

    Да Vulkan не поддерживается в HTML5, хотя все рекламные брошюры выдают кроссплатформенность и широкую поддержку на платформах как основное преимущество Vulkan. По этой причине пришлось написать свой базовый рендер графики для упрощенного типа OpenGL – ES, он используется на мобильных устройствах, иногда не содержит модных фишек современного OpenGL, зато он очень хорошо переносится на WebGL, именно это реализует Emscripten. Написание базового рендера тайлов, bsp рендеринга, для простейшего отображения GUI, и отрисовки моделей + карт, удалось за две недели. Это, пожалуй, была самая сложная часть проекта. Впереди еще очень много работы по имплементации полного функционала рендеринга Surreal Engine, поэтому любая помощь читателей приветствуется в виде кода и pull request’ов.

    OpenAL поддерживается!

    Большим везением стало то, что Surreal Engine использует OpenAL для вывода звука. Написав простой hello world на OpenAL, и собрав его на WebAssembly c помощью Emscripten, мне стало ясно насколько все просто, и я отправился портировать звук.
    После нескольких часов дебага, стало очевидно что в OpenAL реализации Emscripten есть несколько багов, например при инициализации считывания количества моно каналов, метод возвращал бесконечную цифру, а после попытки инициализации вектора бесконечного размера, падает уже C++ с исключением vector::length_error.
    Это удалось обойти сделав хардкод количества моно каналов на 2048:

    		alcGetIntegerv(alDevice, ALC_MONO_SOURCES, 1, &monoSources);
    		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);
    
    #if __EMSCRIPTEN__
    		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?
    #endif
    
    

    А сеть есть?

    Surreal Engine сейчас не поддерживает сетевую игру, игра с ботами поддерживается, но нужен кто-то кто напишет ИИ для этих ботов. Теоретически реализовать сетевую игру на WebAssembly/Emscripten можно с помощью Websockets.

    Заключение

    В заключение хочется сказать что портирование Surreal Engine получилось достаточно гладким из-за использования библиотек для которых есть порты Emscripten, также мой прошлый опыт реализации игры на C++ для WebAssembly на Emscripten. Ниже ссылки на источники знаний, репозиториев по теме.
    M-M-M-MONSTER KILL!
    Также если вы хотите помочь проекту, желательно кодом рендера WebGL/OpenGL ES, то пишите мне в Telegram:
    https://t.me/demenscave

    Ссылки

    https://demensdeum.com/demos/SurrealEngine/

    https://github.com/demensdeum/SurrealEngine-Emscripten

    https://github.com/dpjudas/SurrealEngine

  • Проброс портов между клиентами через Chisel: урезанный тоннель без L3

    Когда два устройства находятся за NAT или строгими фаерволами и не могут «увидеть» друг друга напрямую, стандартным решением кажется VPN. Но полноценный L3-тоннель (вроде WireGuard или OpenVPN) часто избыточен: он требует root-прав, настройки виртуальных интерфейсов и может конфликтовать с существующими маршрутами.

    В таких случаях удобно использовать Chisel — TCP/UDP тоннель, работающий поверх HTTP и использующий WebSockets для передачи данных. В этой заметке я покажу, как «прокинуть» порт от одного клиента к другому через промежуточный сервер.

    Как это работает?

    Представьте ситуацию: у вас есть Клиент А (например, ваш домашний сервер), Клиент Б (ваш рабочий ноутбук) и VPS с публичным IP-адресом. Клиенты А и Б могут обращаться к VPS, но не друг к другу.

    Схема проброса будет выглядеть так:
    1. Клиент А подключается к VPS и открывает «обратный» (reverse) порт на сервере. Теперь всё, что приходит на порт X сервера, попадает на порт Y Клиента А.
    2. Клиент Б подключается к VPS и пробрасывает порт Z со своей локальной машины на порт X сервера.
    3. В итоге Клиент Б обращается к localhost:Z и попадает на Клиент А:Y.

    Такой подход — одно из решений в случаях, когда связь с VPS не реализует L3-слой или отсутствует возможность настройки маршрутизации между клиентами. Мы работаем исключительно на уровне приложений и портов.

    Шаг 1: Запуск сервера

    На вашей VPS достаточно запустить Chisel в режиме сервера. Флаг --reverse обязателен, чтобы разрешить клиентам открывать порты на стороне сервера.

    chisel server --port 8080 --reverse
    

    Шаг 2: Подключение Клиента А (Источник)

    Допустим, Клиент А хочет открыть доступ к своему локальному веб-серверу на порту 3000. Он подключается к VPS и говорит: «зарезервируй на сервере порт 2000 и перенаправляй его мне на 3000».

    chisel client vps-ip:8080 R:2000:127.0.0.1:3000
    

    Теперь порт 2000 на VPS (на loopback интерфейсе) ведет к Клиенту А.

    Шаг 3: Подключение Клиента Б (Потребитель)

    Теперь Клиент Б хочет получить доступ к этому ресурсу. Он подключается к той же VPS и пробрасывает свой локальный порт 8080 на порт 2000 сервера.

    chisel client vps-ip:8080 8080:127.0.0.1:2000
    

    Готово! Теперь открыв http://localhost:8080 на Клиенте Б, вы увидите сервис, запущенный на Клиенте А.

    Безопасность и нюансы

    Chisel поддерживает аутентификацию через флаг --auth, что крайне рекомендуется при работе через публичные сервера. Также можно использовать TLS-сертификаты для шифрования трафика.

    Главное преимущество такого подхода — отсутствие необходимости в TUN/TAP устройствах и сложных таблицах маршрутизации. Это «урезанный» тоннель, который делает ровно одну вещь: связывает порты через WebSocket-соединение. Это работает даже через корпоративные прокси, если настроить Chisel на работу через 443 порт.

    Вывод

    Chisel — это утилита для специфических сетевых задач. Когда нужно прокинуть порты между изолированными узлами без настройки полноценного VPN, связка из прямого и обратного тоннелей через релей-сервер оказывается вполне жизнеспособным решением.

    Ссылки

    https://github.com/jpillora/chisel

  • Почему никак не получается исправить баг?

    Вы часами сидите над кодом, перебираете гипотезы, правите условия, но баг все равно воспроизводится. Знакомо? Это состояние фрустрации часто называют «охотой на призраков». Кажется, что программа живет своей жизнью, игнорируя ваши исправления.

    Одной из самых частых — и самых обидных — причин такой ситуации является поиск ошибки совершенно не в том месте приложения.

    Ловушка «ложных симптомов»

    Когда мы видим ошибку, наше внимание приковано к месту, где она «выстрелила». Но в сложных системах место проявления бага (crash или некорректное значение) — это лишь финал длинной цепочки событий. Пытаясь починить финал, вы боретесь с симптомами, а не с болезнью.

    Именно здесь на помощь приходит концепция блок-схемы.

    Как это работает в реальности

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

    Без понимания этой структуры разработчик часто блуждает в темноте. Представьте ситуацию: вы правите логику в одной ветви условий, в то время как приложение (из-за определенного набора параметров) уходит в совершенно другую ветку, о которой вы даже не задумывались.

    Результат: Вы тратите часы на «идеальное» исправление кода в одной части алгоритма, что, естественно, никак не исправляет проблему в другой его части, где на самом деле происходит сбой.


    Алгоритм победы над багом

    Чтобы перестать биться в закрытую дверь, нужно изменить подход к диагностике:

    • Найдите состояние в дереве исходов: Прежде чем писать код, нужно точно определить путь, по которому прошло приложение. В какой точке логика свернула не туда? Какое именно состояние (State) привело к проблеме?
    • Воспроизведение — это 80% успеха: Обычно это делается силами тестировщиков и автотестов. Если баг «плавающий», к процессу подключается разработка для совместного поиска условий.
    • Используйте максимум информации: Для локализации важны логи, версия ОС, параметры устройства, тип подключения (Wi-Fi/5G) и даже конкретный оператор связи.

    «Фотография» момента ошибки

    В идеале для исправления нужно получить полное состояние приложения в момент воспроизведения бага. Также критически важны логи взаимодействия: они показывают не только финальную точку, но и весь путь пользователя (какие действия предшествовали сбою). Это помогает понять, как воссоздать подобное состояние снова.

    Совет на будущее: Если вы столкнулись со сложным кейсом, добавьте в этот участок кода расширенную отладочную информацию (debug logging) на случай, если ситуация повторится.


    Проблема «неуловимых» состояний в эпоху AI

    В современных системах, использующих LLM (Large Language Models), классический детерминизм («один вход — один выход») часто нарушается. Вы можете передать абсолютно те же входные данные, но получить другой результат.

    Это происходит из-за недетерминированности современных продакшн-систем:

    • Параллелизм на GPU: Операции с плавающей запятой в GPU не всегда ассоциативны. Из-за параллельного выполнения потоков порядок сложения чисел может незначительно меняться, что влияет на результат.
    • Температура GPU и троттлинг: Скорость выполнения и распределение нагрузки могут зависеть от физического состояния «железа». В огромных моделях эти микроскопические различия накапливаются и могут привести к выбору другого токена на выходе.
    • Динамический батчинг: В облаке ваш запрос объединяется с другими. Разный размер пакета (batch size) меняет математику вычислений в ядрах.

    В таких условиях воспроизвести «то самое состояние» становится практически невозможно. Здесь спасает только статистический подход к тестированию.


    Когда логика бессильна: Проблемы с памятью

    Если вы работаете с «unsafe» языками (C или C++), баг может возникать из-за разрушения памяти (Memory Corruption).

    Это самые тяжелые случаи: ошибка в одном модуле может «затереть» данные в другом. Это приводит к совершенно необъяснимым и единичным сбоям, которые невозможно отследить по обычной логике приложения.

    Как защититься на уровне архитектуры?

    Чтобы избежать таких «мистических» багов, стоит использовать современные подходы:

    • Паттерны многопоточного программирования: Четкая синхронизация исключает состояние гонки (race conditions).
    • Языки с безопасной многопоточностью: Инструменты, гарантирующие безопасность памяти еще на этапе компиляции:
      • Rust: Система владения (Ownership) исключает ошибки памяти.
      • Swift 6 Concurrency: Строгие проверки изоляции данных.
      • Erlang: Полная изоляция процессов через модель акторов.

    Резюме

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

  • Почему документация — ваш лучший друг

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

    «Apps may only use public APIs and must run on the currently shipping OS.» Apple App Review Guidelines

    Если вы когда-либо начинали работу с новым фреймворком и ловили себя на мысли: «Сейчас всё сам пойму, читать документацию — это слишком долго», вы точно не одиноки. У многих из нас срабатывает естественный исследовательский инстинкт: сначала попробовать, и только потом — заглядывать в инструкции. И это совершенно нормально.

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

    Почему просто «разобраться самому» иногда бывает недостаточно?

    Фреймворки, особенно закрытые, представляют собой сложные и многослойные системы. В них часто скрывается внутренняя логика и оптимизации, которые:

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

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

    Документация — это не ограничение, а надежная опора

    Разработчики фреймворков создают мануалы, чтобы помочь нам. Действуя в рамках документации, мы получаем:

    * стабильность;
    * поддержку;
    * предсказуемое поведение системы.

    Выходя за эти рамки, мы берем на себя дополнительные риски, и поддерживать такой код становится сложнее.

    Эксперименты? Конечно. Но с пониманием границ.
    Любопытство — прекрасная черта разработчика. Исследовать и пробовать новое абсолютно необходимо. Но здесь есть небольшое пожелание:

    Экспериментировать комфортнее всего с опорой на best practices.

    Документация — это карта, которая показывает, какие пути наиболее безопасны и поддерживаются создателями.

    Взгляд со стороны: советы экспертов

    Часто мы учимся у опытных коллег:

    * они проводят полезные курсы,
    * выступают на конференциях,
    * пишут замечательные книги и блоги,
    * делятся своим уникальным видением.

    Многие из них делятся действительно ценным опытом. Но стоит помнить: если авторские подходы идут вразрез с официальной документацией, они могут оказаться хрупкими.

    Такие «эмпирические паттерны» порой:

    * работают только на конкретной версии фреймворка;
    * чувствительны к обновлениям;
    * могут вести себя непредсказуемо в нестандартных ситуациях.

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

    Немного о SOLID

    Три идеи из принципов SOLID отлично дополняют этот подход:

    * Open/Closed Principle: старайтесь расширять поведение через публичные API и по возможности не зависеть от скрытой реализации.
    * Liskov Substitution Principle: полагайтесь на контракт, а не на конкретную реализацию. Иначе изменения под капотом могут привести к неожиданным сложностям.
    * Dependency Inversion: стройте зависимости на абстракциях, а не на деталях.

    На практике это значит, что привязка к внутренним, недокументированным деталям фреймворка делает систему хрупкой.
    Опираясь на публичные интерфейсы и контракты, мы получаем:

    * лучшую изоляцию кода от изменений во фреймворке;
    * удобство в тестировании;
    * предсказуемость и надежность архитектуры.

    А если встретился баг?

    Случается и так, что всё сделано по правилам, но результат не соответствует ожиданиям. Фреймворки развиваются и не всегда идеальны. В таких случаях:

    * Соберите минимальный пример, на котором воспроизводится проблема.
    * Убедитесь, что используются только задокументированные API.
    * Отправьте баг-репорт — команда разработчиков наверняка оценит ваш труд и постарается помочь.

    Если же пример опирается на обходные пути, разработчикам будет гораздо сложнее оказать поддержку.

    Как выжать максимум из фреймворка

    * Обращайтесь к документации.
    * Придерживайтесь гайдов и рекомендаций авторов.
    * Экспериментируйте в пределах описанного функционала.
    * Сверяйте советы из сети с официальными источниками.
    * Локализуйте баги с уважением к контрактам фреймворка.

    Заключение

    Фреймворки — это мощные инструменты со своими правилами игры. Забывая о них, мы рискуем сделать наш код излишне уязвимым. А ведь всем нам хочется, чтобы созданные продукты жили долго и не требовали срочных исправлений после каждого минорного апдейта.

    Мануалы и документация — это отличная поддержка, которая помогает создавать по-настоящему надежные решения.

    Источники

    https://developer.apple.com/app-store/review/guidelines/
    https://en.wikipedia.org/wiki/SOLID
    https://en.wikipedia.org/wiki/API
    https://en.wikipedia.org/wiki/RTFM

  • Сборка C++ SDL приложения для iOS на Linux

    В данной заметке я опишу процедуру сборки C++ SDL приложения для iOS на Linux, подпись ipa архива без платной подписки Apple Developer и установку на чистое устройство (iPad) с помощью macOS без Jailbreak.

    Для начала установим тулчейн сборки для Linux:
    https://github.com/tpoechtrager/cctools-port

    Тулчейн нужно выгрузить из репозитория, далее по инструкции на сайте Godot Engine закончить установку:
    https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

    На данный момент требуется скачать Xcode dmg и скопировать оттуда sdk для сборки cctools-port. Данный этап проще проходить на macOS, достаточно скопировать из установленного Xcode необходимые файлы sdk. После успешной сборки, в терминале будет путь к тулчейну кросскомпилятора.
    Далее можно приступать к сборке SDL приложения для iOS. Откроем cmake и добавим необходимые изменения для сборки C++ кода:

    SET(CMAKE_SYSTEM_NAME Darwin)
    SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
    SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
    SET(CMAKE_LINKER arm-apple-darwin11-ld)
    
    

    Теперь можно собирать с помощью cmake и make, но не забудьте прописать $PATH к тулчейну кросскомпилятора:

    
    PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin
    
    

    Для корректной линковки с фреймворками и SDL прописываем их в cmake, зависимости игры Space Jaguar для примера:

    
    target_link_libraries(
    ${FSEGT_PROJECT_NAME}
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
    )
    
    

    В моем случае библиотеки SDL, SDL_Image, SDL_mixer скомпилированы в Xcode на macOS заранее для статичной линковки; Фреймворки скопированы из Xcode. Также добавлена библиотека libclang_rt.ios.a, которая включает в себя специфические рантайм вызовы iOS, например isOSVersionAtLeast. Включен макрос для работы с OpenGL ES, отключение неподдерживаемых функций в мобильной версии, по аналогии с Android.
    После решения всех проблем сборки, вы должны получить собранный binary для arm. Далее рассмотрим запуск собранного бинарика на устройстве без Jailbreak.
    На macOS произведите установку Xcode, зарегистрируйтесь на портале Apple, без оплаты программы для разработчиков. Добавьте аккаунт в Xcode -> Preferences -> Accounts, создайте пустое приложение и соберите на реальном устройстве. Во время сборки устройство будет добавлено к бесплатному аккаунту разработчика. После сборки и запуска, нужно произвести сборку архива, для этого выберете Generic iOS Device и Product -> Archive. По окончанию сборки архива достаньте из него файлы embedded.mobileprovision, PkgInfo. Из лога сборки на устройство найдите строку codesign с корректным ключом подписи, путь к файлу entitlements с расширением app.xcent, скопируйте его.
    Скопируйте папку .app из архива, замените бинарик в архиве на собранный кросскомпилятором в линуксе (например SpaceJaguar.app/SpaceJaguar), далее добавляем в .app необходимые ресурсы, проверьте сохранность PkgInfo и embedded.mobileprovision файлов в .app из архива, скопируйте заново если необходимо. Переподписываем .app с помощью команды codesign – codesign требует на вход ключ для sign, путь к файлу entitlements (можно переименовать с расширением .plist)
    После переподписывания создайте папку Payload, перенесите туда папку с расширением .app, создайте zip архив с Payload в корне, переименуйте архив с расширением .ipa. После этого в Xcode откройте список устройств и сделайте Drag’n’Drop нового ipa в список приложений устройства; Установка через Apple Configurator 2 для данного способа не работает. Если переподписывание произведено корректно, то приложение с новым бинариком будет установлено на iOS устройство (например iPad) с 7 дневным сертификатом, на период тестирования этого достаточно.

    Источники

    https://github.com/tpoechtrager/cctools-port

    https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

    https://jonnyzzz.com/blog/2018/06/13/link-error-3/

    https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone

    https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html

  • Сборка для Windows под Ubuntu MinGW CMake

    В данной заметке я опишу процесс сборки библиотек и приложений для Windows с помощью тулчейна MinGW32 на Ubuntu.
    Установим wine, mingw:

    sudo apt-get install wine mingw-w64
    

    После этого уже можно собирать C/C++ приложения под Windows:

    # C
    i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe      # 32-bit
    x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe    # 64-bit
     
    # C++
    i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe     # 32-bit
    x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe   # 64-bit
    

    Собранные exe можно проверить с помощью wine.
    Далее рассмотрим изменения сборки CMake, файла CMakeLists.txt, добавляем MinGW специфичные вещи к файлу сборки:

    if (MINGW32)
    set(CMAKE_SYSTEM_NAME Windows)
    SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
    SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
    SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
    set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
    endif()
    
    // для сборки shared dll
    elseif (MINGW32)
    add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
    else()
    
    // обязательно линкуем со всеми зависимостями
    if (MINGW32)
    target_link_libraries(
                            FlameSteelEngineGameToolkit.dll 
                            -static-libgcc
                            -static-libstdc++
                            SDL2 
                            SDL2_mixer 
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)
    
    set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
            PREFIX ""
            SUFFIX ""
            LINK_FLAGS "-Wl,--add-stdcall-alias"
            POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning; 
            # MinGW generates position-independent-code for DLL by default
    )
    else()
    

    Собираем:

    cmake -DMINGW32=1 .
    make
    

    На выходе будет dll или exe, смотря что вы собираете. За рабочим примером можете смотреть в репозиторий нового проекта Cube-Art-Project и его библиотеки:
    https://gitlab.com/demensdeum/cube-art-project

    https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL

    https://gitlab.com/demensdeum/cube-art-project-bootstrap

    Источники
    https://arrayfire.com/cross-compile-to-windows-from-linux/

  • Сборка macOS приложений под Ubuntu OSXCross CMake

    В этой заметке я опишу сборку кросплатформенных C++ приложений для macOS на сборочной машине Ubuntu с использованием CMake и osxcross.
    Для начала устанавливаем тулчейн osxcross:
    https://github.com/tpoechtrager/osxcross
    Установка происходит в 3 этапа, загрузка зависимостей:

    cd tools
    ./get_dependencies.sh
    

    Загрузка XCode.xip с официального сайта Apple, далее выгрузка SDK из XCode:

    ./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip
    

    Надеюсь вы прочитали лицензионное соглашение XCode на прошлом шаге? Далее сборка тулчейна с нужным префиксом:

    INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh 
    

    Теперь можно пользоваться osxcross из директории-префикса прошлого шага. Добавим новый макрос сборки для CMake, пропишем все необходимо:

    if (OSXCROSS)
    SET(CMAKE_SYSTEM_NAME Darwin)
    SET(CMAKE_C_COMPILER o64-clang)
    SET(CMAKE_CXX_COMPILER o64-clang++)
    SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
    SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
    SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
    SET(ENV{OSXCROSS_MP_INC} 1)
    endif()
    

    Динамическая линковка мне не удалась, поэтому экспортируем библиотеки статически:

    if (OSXCROSS)
    add_library(FlameSteelCore STATIC ${SOURCE_FILES})
    else()
    

    Далее вы можете столкнуться с фактом того что у вас нет необходимых библиотек для osxcross, с этим я встретился при использовании SDL2. osxcross поддерживает готовые пакеты библиотек – macports. Для примера установка SDL2-mixer:

    osxcross-macports -v install libsdl2_mixer
    

    После этого можно приступать к сборке библиотек/приложений как обычно в связке cmake-make, не забудьте прописать статическую линковку библиотек если необходимо.

    Ручная сборка библиотек

    На текущий момент я встретился с проблемой некорректной архивации библиотек при статической линковке, при сборке итогового приложения получаю ошибку:

    file was built for archive which is not the architecture being linked (x86_64)
    

    Очень похоже на этот тикет, удалось реализовать workaround в результате чего сборка завершается корректно. Разархивируем статическую библиотеку и соберем ее по новой с помощью архиватора osxcross:

    ar x ../libFlameSteelCore.a
    rm ../libFlameSteelCore.a
    x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o
    

    Также одной из проблем лично я считаю отсутствие возможности запуска приложений macOS сразу на убунту (хотябы с частью функционала), конечно есть проект darling, но поддержка оставляет пока желать лучшего.

    Источники

    https://github.com/tpoechtrager/osxcross

  • Локальная генерация изображений: ComfyUI и модель FLUX

    В наше время не обязательно полагаться на облачные сервисы: вы можете генерировать высококачественные изображения полностью на своем железе. В этой заметке я опишу, как запустить современную модель FLUX локально на вашем компьютере с помощью ComfyUI.

    ComfyUI использует узловую (node-based) архитектуру. Это позволяет:
    — Тотально контролировать каждый этап генерации.
    — Легко обмениваться готовыми «рабочими процессами» (workflows)

    FLUX — модель крупная, поэтому требования к железу выше, чем у SD 1.5 или SDXL:
    — Видеокарта (GPU): Nvidia RTX с 12 ГБ VRAM и выше (для комфортной работы). Если у вас 8 ГБ или меньше, придется использовать квантованные версии (GGUF или NF4).
    — Оперативная память (RAM): минимум 16 ГБ (лучше 32 ГБ и выше).
    — Место на диске: около 20–50 ГБ для моделей и компонентов.

    Самый простой способ запустить FLUX — использовать готовый шаблон. Просто найдите flux text to image в окне workflows и установите.

    Напишите промпт на английском языке в узле `Text to Image (Flux.1 Dev)`, выберите разрешение (FLUX отлично справляется с 1024×1024 и даже выше) и нажмите RUN.

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

    https://github.com/comfyanonymous/ComfyUI

  • Локальный Vibe-кодинг: LM Studio, VS Code и Continue

    Если у вас было желание использовать нейросети для помощи в написании кода (так называемый Vibe-кодинг), и у вас достаточно мощный компьютер, например с видеокартой Nvidia RTX, то вы можете развернуть всю среду абсолютно бесплатно на своей машине. Это решает проблемы с платными подписками и позволяет спокойно работать с проектами под NDA, так как ваш код никуда не отправляется. В этой заметке я опишу, как собрать локальную связку из LM Studio, VS Code и расширения Continue.

    Инструменты для локального Vibe-кодинга

    Для комфортной работы нам понадобятся три основных компонента:
    — LM Studio: удобное приложение для загрузки и запуска локальных LLM. Оно берет на себя всю сложность работы с GGUF-моделями и поднимает локальный сервер, совместимый с API OpenAI.
    — VS Code: популярный и привычный редактор кода.
    — Continue: расширение для VS Code, которое интегрирует нейросети прямо в рабочую среду. Позволяет общаться в чате, выделять код для рефакторинга и поддерживает автодополнение (autocomplete).

    Требования к железу

    Локальные языковые модели требовательны к памяти:
    — Видеокарта (GPU): Nvidia с 8 ГБ VRAM и выше (для комфортной работы с моделями на 7-8 миллиардов параметров). Для более тяжелых моделей потребуется от 16 ГБ VRAM.
    — Место на диске: около 500 ГБ для хранения разных скачанных моделей.

    Настройка связки

    Процесс настройки достаточно прост и не требует сложных манипуляций в терминале:
    1. Скачайте и установите LM Studio. Во встроенном поиске найдите легковесную модель, например Qwen Coder или gemma3:12b.
    2. В LM Studio перейдите во вкладку Local Server и нажмите Start Server. По умолчанию он запустится на `http://localhost:1234/v1`.
    3. Откройте VS Code и установите расширение Continue из магазина плагинов.
    4. Откройте конфигурационный файл Continue и добавьте новую модель, указав провайдера `openai` и адрес вашего локального сервера из LM Studio.

    После этого вы сможете общаться с локальной LLM прямо в сайдбаре Continue, задавать вопросы по вашему коду и генерировать новые компоненты.

    Почему это работает?

    Как я уже писал ранее, LLM лучше справляются с плоской структурой и WET-кодом (Write Everything Twice). Локальные модели параметров могут уступают гигантам вроде GPT-4 в проектировании сложных архитектур, но для генерации шаблонного кода, рефакторинга простых функций и быстрого прототипирования их возможностей более чем достаточно.

    Кроме того, при локальном Vibe-кодинге ваш код не покидает пределы машины. Это делает такую связку идеальной для корпоративной разработки и работы с чувствительными данными.

    Вывод

    Локальные нейросети не способны полноценно заменить программиста или спроектировать сложную систему. Тем не менее, связка LM Studio + VS Code + Continue дает независимость от облачных сервисов и сохраняет приватность. Это вполне рабочий вспомогательный инструмент для рутинных задач, если вы готовы мириться с ограничениями небольших моделей и самостоятельно контролировать архитектуру проекта.

    Ссылки

    https://code.visualstudio.com/
    https://lmstudio.ai/
    https://continue.dev/

    Источники

    https://youtu.be/IqqCwhG46jY
    https://www.youtube.com/watch?v=7AImkA96mE8

  • Локальная генерация видео: ComfyUI и LTX-2.3

    Раньше создание видео с помощью нейросетей было прерогативой облачных сервисов вроде Runway или Luma. Сегодня, если у вас есть современная видеокарта Nvidia, вы можете генерировать качественные ролики прямо на своем компьютере. В этой заметке я расскажу, как настроить локальную генерацию видео с помощью ComfyUI и эффективной модели LTX-2.3.

    Инструменты для видео-генерации

    Для работы нам понадобятся:
    — ComfyUI: мощный интерфейс с узловой архитектурой, который позволяет гибко настраивать процесс генерации.
    — LTX-2.3: современная модель от Lightricks, оптимизированная для создания плавных и детализированных видео при относительно умеренных требованиях к видеопамяти.

    Требования к железу

    Генерация видео — процесс куда более ресурсоемкий, чем работа с изображениями:
    — Видеокарта (GPU): Nvidia RTX с 8 ГБ VRAM — это необходимый минимум для разрешения 768×512. Для комфортной работы и более высоких разрешений крайне желательно иметь 16–24 ГБ VRAM.
    — Оперативная память (RAM): минимум 32 ГБ. Видео-модели и VAE занимают много места при загрузке.
    — Место на диске: около 500 ГБ для самой модели и сопутствующих компонентов.

    Настройка и запуск

    Процесс запуска LTX-2.3 в ComfyUI выглядит следующим образом:
    1. Обновите ComfyUI: Модель относительно новая, поэтому убедитесь, что у вас установлена последняя версия интерфейса.
    2. Установите Workflow: Самый простой способ — найти готовый JSON-шаблон для LTX Video. Модель требует специфических узлов для работы с латентным пространством видео.
    3. Промпт и параметры: Введите описание сцены на английском языке. Обратите внимание, что LTX-2.3 хорошо понимает движение (например, «camera orbits around», «fast movement»).

    Почему стоит выбрать LTX-2.3?

    LTX-2.3 примечательна тем, что она дает результат, сопоставимый с закрытыми облачными сервисами, но работает локально. Это дает вам:
    — Полную приватность: ваши промпты и сгенерированные видео не уходят на чужие сервера.
    — Контроль: вы можете экспериментировать с частотой кадров (FPS), разрешением и силой влияния промпта без необходимости платить за каждую попытку.

    Локальная генерация видео всё еще находится в стадии активного развития, и LTX-2.3 — это отличный входной билет в мир «домашнего Голливуда».

    Ссылки

    https://github.com/comfyanonymous/ComfyUI
    https://huggingface.co/Lightricks/LTX-Video

  • Локальная генерация музыки: ComfyUI и модель ACE-Step-1.5

    В наше время не обязательно полагаться на облачные сервисы для создания контента: вы можете генерировать высококачественную музыку полностью на своем железе. В этой заметке я опишу, как запустить современную модель ACE-Step-1.5 локально на вашем компьютере с помощью ComfyUI.

    ComfyUI использует узловую (node-based) архитектуру. Это позволяет:
    — Тотально контролировать каждый этап генерации аудио.
    — Легко обмениваться готовыми «рабочими процессами» (workflows).

    ACE-Step-1.5 — это продвинутая модель для генерации музыки, требующая значительных вычислительных ресурсов. Требования к железу выше, чем у многих простых синтезаторов:
    — Видеокарта (GPU): Nvidia RTX с 8 ГБ VRAM и выше (рекомендуется 12 ГБ+) для комфортной работы при высоком качестве.
    — Оперативная память (RAM): минимум 16 ГБ (лучше 32 ГБ и выше).
    — Процессор (CPU): Современный многоядерный процессор с хорошей поддержкой AVX/CUDA вычислений.
    — Место на диске: около 20–50 ГБ для моделей и компонентов.

    Самый простой способ запустить ACE-Step-1.5 — использовать готовый шаблон аудиогенерации. Просто найдите music text to audio в окне workflows и установите.

    Напишите промпт, описывающий жанр и настроение (например, «uplifting synthwave track with heavy bass»), в узле `Prompt Input`. Укажите желаемую длительность и нажмите RUN.
    Первая генерация может занять время, так как модели будут загружаться в память видеокарты и обрабатывать сложные акустические паттерны.

    https://github.com/comfyanonymous/ComfyUI
    https://www.youtube.com/watch?v=UAlLD5fS7-c

  • Локальные нейросети с помощью ollama

    Если у вас было желание запустить подобие ChatGPT и у вас достаточно мощный компьютер, например с видеокартой Nvidia RTX, то тогда вы можете запустить проект ollama, который позволит использовать одну из готовых LLM моделей, на локальный машине, абсолютно бесплатно. ollama обеспечивает возможность общения с LLM моделями, на манер ChatGPT, также в последней версии объявлена возможность прочтения изображений, форматирование выходных данных в формат json.

    Сам проект я запускал также и на макбуке с процессором Apple M2, и мне известно что поддерживаются последние модели видеокарт от AMD.

    Для установки на macOS зайдите на сайт ollama:
    https://ollama.com/download/mac

    Нажмите «Download for macOS», у вас загрузится архив вида ollama-darwin.zip, внутри архива будет Ollama.app который нужно скопировать в «Applications». После этого запускайте Ollama.app, скорее всего при первом запуске произойдет процесс установки. После этого в трее вы увиделе иконку ollama, трэй это справа сверху рядом с часами.

    После этого запускайте обычный терминал macOS, и набирайте команду загрузки, установки и запуска любой модели ollama. Список доступных моделей, описания, их характеристик можно увидеть на сайте ollama:
    https://ollama.com/search

    Выбирайте модель с наименьшим количеством параметров, если она не влезает в вашу видеокарту на запуске.

    Для примера команда запуска модели llama3.1:latest:

    ollama run llama3.1:latest
    

    Установка для Windows и Linux в целом похожа, в одном случае будет установщик ollama и дальнейшая работа с ней через Powershell.
    Для Linux установка производится скриптом, однако я рекомендую использовать версию конкретно вашего пакетного менеджера. В Linux ollama запустить также можно через обычный терминал bash.

    Источники
    https://www.youtube.com/watch?v=Wjrdr0NU4Sk
    https://ollama.com

  • Стабилизация видео с помощью ffmpeg

    Если вы хотите стабилизировать видео и убрать дрожание камеры, инструмент `ffmpeg` предлагает мощное решение. Благодаря встроенным фильтрам `vidstabdetect` и `vidstabtransform`, можно добиться профессионального результата без использования сложных видеоредакторов.

    Подготовка к работе

    Прежде чем начать, убедитесь, что ваш `ffmpeg` поддерживает библиотеку `vidstab`. В Linux это можно проверить командой:

    bash  
    ffmpeg -filters | grep vidstab  
    

    Если библиотека не установлена, её можно добавить:

    sudo apt install ffmpeg libvidstab-dev  
    

    Установка для macOS через brew:

    brew install libvidstab
    brew install ffmpeg
    

    Теперь перейдём к процессу.

    Шаг 1: Анализ движения

    Сначала нужно провести анализ движения видео и создать файл с параметрами стабилизации.

    ffmpeg -i input.mp4 -vf vidstabdetect=shakiness=10:accuracy=15 transfile=transforms.trf -f null -  
    

    Параметры:

    shakiness: Уровень дрожания видео (по умолчанию 5, можно увеличить до 10 для более сложных случаев).
    accuracy: Точность анализа (по умолчанию 15).
    transfile: Имя файла для сохранения параметров движения.

    Шаг 2: Применение стабилизации

    Теперь можно применить стабилизацию, используя файл трансформаций:

    ffmpeg -i input.mp4 -vf vidstabtransform=input=transforms.trf:zoom=5 output.mp4
    

    Параметры:

    input: Указывает на файл с параметрами трансформации (созданный на первом шаге).
    zoom: Коэффициент масштабирования для устранения черных краев (например, 5 — автоматическое увеличение до устранения артефактов).

  • Вычислительные машины Тьюринга

    Представляю вашему вниманию перевод первых страниц статьи Алана Тьюринга – “О ВЫЧИСЛИМЫХ ЧИСЛАХ С ПРИЛОЖЕНИЕМ К ПРОБЛЕМЕ РАЗРЕШЕНИЯ” 1936 года. Первые главы содержат описание вычислительных машин, которые в дальнейшем стали основой для современной вычислительной техники.

    Полный перевод статьи и пояснение можно прочитать в книге американского популяризатора Чарлза Петцольда, под названием “Читаем Тьюринга. Путешествие по исторической статье Тьюринга о вычислимости и машинах Тьюринга” (ISBN 978-5-97060-231-7, 978-0-470-22905-7)

    Оригинальная статья:

    https://www.astro.puc.cl/~rparra/tools/PAPERS/turing_1936.pdf

    О ВЫЧИСЛИМЫХ ЧИСЛАХ С ПРИЛОЖЕНИЕМ К ПРОБЛЕМЕ РАЗРЕШЕНИЯ

    А. М. ТЬЮРИНГ

    [Получено 28 мая 1936 г. — Прочитано 12 ноября 1936 г.]

    «Вычислимые» (computable) числа могут быть кратко описаны как действительные числа, выражения которых в виде десятичных дробей исчислимы (calculable) конечным числом средств. Хотя на первый взгляд в данной статье как вычислимые рассматриваются именно числа, почти так же легко определять и исследовать вычислимые функции целой переменной, действительной переменной, вычислимой переменной, вычислимые предикаты и тому подобное. Однако же, фундаментальные проблемы, связанные с указанными вычислимыми объектами, в каждом случае одни и те же. Для детального рассмотрения в качестве вычислимого объекта я выбрал именно вычислимые числа потому, что методика их рассмотрения наименее громоздкая. Надеюсь в скором времени описать взаимоотношения вычислимых чисел с вычислимыми функциями и так далее. При этом будут проведены исследования в области теории функций действительной переменной, выраженной в терминах вычислимых чисел. По моему определению, действительное число является вычислимым, если его представление в виде десятичной дроби может быть записано машиной.

    В параграфах 9 и 10 я привожу некоторые доводы, чтобы показать, что вычислимые числа включают в себя все числа, которые естественно считать вычислимыми. В частности, я показываю, что некоторые большие классы чисел вычислимы. Они включают, например, действительные части всех алгебраических чисел, действительные части нулей функций Бесселя, числа π, e и прочие. Однако вычислимые числа включают в себя не все определимые числа, в подтверждение чего приведен пример определимого числа, которое не является вычислимым.

    Хотя класс вычислимых чисел очень велик и во многих отношениях похож на класс действительных чисел, он все же поддается перечислению, то есть является перечислимым (enumerable). В §8 я рассматриваю определенные доводы, которые, казалось бы, доказывают обратное предположение. При корректном применении одного из этих доводов делаются выводы, на первый взгляд, аналогичные выводам Геделя*. Данные результаты имеют чрезвычайно важные способы применения. В частности, как показано ниже (§11), не может иметь решения проблема разрешения.

    В своей недавней статье Алонзо Черч** представил идею «способности поддаваться эффективному исчислению» (effective calculability), которая эквивалентна моей идее «вычислимости» (computability), но имеет совершенно иное определение. Черч также приходит к аналогичным выводам относительно проблемы разрешения. Доказательство эквивалентности «вычислимости» и «способности поддаваться эффективному исчислению» изложено в приложении к настоящей статье.

    1. Вычислительные машины

    Мы уже говорили, что вычислимые числа — это такие числа, десятичные разряды которых исчислимы конечными средствами. Тут требуется более четкое определение. В настоящей статье не будет предпринято никаких реальных попыток обосновать приведенные здесь определения до тех пор, пока мы не дойдем до §9. Пока лишь замечу, что (логическое) обоснование (этого) заключается в том, что человеческая память в силу необходимости ограничена.

    Сопоставим человека в процессе вычисления действительного числа с машиной, которая способна выполнять только конечное число условий q1, q2, …, qR; назовем эти условия «m-конфигурациями». Данная (то есть так определенная) машина снабжена «лентой» (аналогом бумаги). Такая лента, проходящая через машину, разделена на секции. Назовем их «квадратами». Каждый такой квадрат может содержать какой-то «символ». В любой момент существует и при том только один такой квадрат, скажем, r-й, содержащий символ который находится «в данной машине». Назовем такой квадрат «отсканированным символом». «Отсканированный символ» — это единственный такой символ, о котором машина, образно выражаясь, «непосредственно осведомлена». Однако при изменении своей m-конфигурации машина может эффективно запоминать некоторые символы, которые она «увидела» (отсканировала) ранее. Возможное поведение машины в любой момент определяется m-конфигурацией qn и отсканированным символом***. Назовем данную пару символов qn, «конфигурацией». Обозначенная таким образом конфигурация определяет возможное поведение данной машины. В некоторых из таких конфигураций, в которых отсканированный квадрат является пустым (т. е. не содержит символа), данная машина записывает новый символ на отсканированном квадрате, а в других из таких конфигураций она стирает отсканированный символ. Данная машина также способна перейти к сканированию другого квадрата, но так она может переместиться лишь на соседний квадрат вправо или влево. В дополнение к любой из этих операций можно изменить m-конфигурацию машины. При этом некоторые из записанных символов сформируют последовательность цифр, являющуюся десятичной частью вычисляемого действительного числа. Остальные из них представят собой не более чем неточные отметки с тем, чтобы «помочь памяти». При этом стиранию могут подвергаться лишь вышеупомянутые неточные отметки.

    Я утверждаю, что рассматриваемые здесь операции включают в себя все те операции, которые используются при вычислении. Обоснование данного утверждения легче понять читателю, имеющему представление о теории машин. Поэтому в следующем разделе я продолжу развивать рассматриваемую теорию, опираясь на понимание значения терминов «машина», «лента», «отсканированный» и т. д.

    *Гедель «О формально неразрешимых предложениях «Принципов математики» (опубликованных Уайтехдом и Расселом в 1910, 1912 и 1913 гг.) и родственных систем, часть I», журнал по мат. физике, ежемесячный бюллетень на немецком языке № 38 (за 1931 год , стр. 173-198.

    ** Алонзо Черч, «Неразрешимая проблема элементарной теории чисел», American J. of Math., («Американский журнал математики»), № 58 (за 1936 год), стр. 345-363.

    *** Алонсо Черч, «Замечание о проблеме разрешения», J. of Symbolic Logic («Журнал математической логики»), №1 (за 1936 год), стр. 40-41

DemensDeum
Обзор конфиденциальности

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