Cluster · разработчикам
HTTP-интерфейс
HTTP-служба принимает запросы внешнего интерфейса и обращается к владельцу Mekansm через закрытый Unix-сокет. Сама база в HTTP-процессе не открывается. Служба предоставляет /api/, готового сайта и раздачи файлов в пакете нет. Запросы к / и /static/ возвращают 404.
Сайт или Mini App размещается отдельно. Для браузера страница и /api/ должны использовать один Origin: прокси передаёт /api/* HTTP-службе, а остальные пути обслуживает сайт. CORS в службе не настроен. --origin задаёт адрес этого общего узла, включая порт для локальной разработки.
Запуск
Сначала запускается ядро с отдельным ключом. Тестовая покупка включается отдельным флагом:
.venv/bin/cluster --path ./data serve --key-file /path/to/core.key --test-payments
.venv/bin/cluster --path ./data api --origin https://cluster.example.comHTTP-служба слушает 127.0.0.1. Порт по умолчанию 8080. Флаг --port меняет только этот локальный порт и не ослабляет проверку --origin. HTTPS завершается на настроенном прокси. Если используется --proxy, прокси обязан перезаписывать X-Real-IP адресом посетителя, а не передавать значение посетителя. Без этого флага заголовок игнорируется, учитывается адрес прямого соединения.
Для локальной проверки разрешён --origin http://127.0.0.1:8080. В этом режиме cookie не имеет признака Secure. Для внешнего адреса нужен HTTPS.
--test-payments разрешает тестовую активацию всем зарегистрированным пользователям этой службы. Денежных списаний нет. Этот режим предназначен для закрытого этапа с тестовыми профилями.
Формат
Для начала формат ответа. Типы ниже описывают JSON и не требуют импорта из Cluster:
from typing import Literal, TypedDict
type Json = None | bool | int | float | str | list[Json] | dict[str, Json]
class Success(TypedDict):
ok: Literal[True]
result: Json
class Failure(TypedDict):
ok: Literal[False]
error: str
type Response = Success | FailureОтвет: {"ok": true, "result": ...} или {"ok": false, "error": ...}. Ответы не кэшируются. Тело JSON ограничено 64 KiB, у portal/request 128 KiB для порционной загрузки файла в base64. Повторяющиеся поля, неконечные числа и служебные параметры отклоняются.
Все изменяющие запросы, включая вход и регистрацию, используют POST, Content-Type: application/json и Origin, совпадающий с адресом службы. Вход создаёт __Host-cluster с HttpOnly, Secure и SameSite=Strict. В обычном браузере ключ сеанса не возвращается в JSON. Для Mini App заголовок X-Cluster-Client: mini выбирает вход через access и csrf в ответе, которые страница держит только в памяти. В следующих запросах используются Authorization: Bearer ACCESS и X-Cluster-CSRF, Origin по-прежнему проверяется. В обычном браузере CSRF находится в __Host-cluster-csrf и передаётся тем же заголовком.
Все операции из таблиц начинаются с /api/. Параметры POST передаются в JSON-теле. Параметры GET передаются в строке запроса. Cookie и CSRF добавляются отдельно, не являются полями тела.
Типы параметров
Идентификаторы профиля, юзербота, публикации, файла и запроса имеют тип str. Telegram ID и ID чата имеют тип int: это другие идентификаторы, строка ID профиля их не заменяет. Одноразовые коды передаются строкой, чтобы сохранить начальные нули.
values, data, manifest, rules содержат объекты JSON. revision является целым номером версии настроек. remove, replace_bot и public являются переключателями bool. Время передаётся числом секунд Unix, длительности задаются в секундах. Значения, разрешённые для конкретного метода, перечислены в его описании. Неконечные числа и произвольные Python-объекты не принимаются.
Регистрация и вход
| POST | Параметры |
|---|---|
auth/register | login, address, password, terms=true, age_group (13-17 или 18+), для несовершеннолетних guardian=true |
auth/resend | address |
auth/redeem | mail_code, purpose, для reset ещё password |
auth/reset | address |
auth/login | login, password, при TOTP ещё code |
auth/mini | подписанный data, при TOTP ещё code |
Назначения почтового кода: verify, reset, email. После регистрации код отправляется письмом. Ответ регистрации его не содержит.
Профиль
GET auth/profile возвращает ID, логин, почту, состояние, ID юзербота и привязанный Telegram ID. Хеш пароля, TOTP и сессия Telegram не включаются.
Следующие POST требуют входа и CSRF:
| POST | Параметры |
|---|---|
auth/logout | без параметров |
auth/totp/start | password |
auth/totp/finish | code |
auth/password | password, new_password, при TOTP ещё code |
auth/email | address, password, code |
auth/bind | подписанный data, password, code |
auth/delete | password, code |
auth/restore | password, code |
auth/owner | id юзербота, telegram нового овнера, password, code, необязательный remove |
subscription/checkout | period, уникальный request |
Периоды тестовой покупки: month 30 суток, quarter 90 суток, year 365 суток. Повтор request не продлевает второй раз. Без --test-payments операция недоступна.
После включения TOTP или смены Telegram-привязки сервер заменяет cookie. После смены пароля и подтверждения смены почты нужно войти заново.
Telegram
| Метод | Параметры |
|---|---|
POST telegram/begin | phone, api_id, api_hash, при смене аккаунта password Cluster и code второго фактора, необязательный proxy |
POST telegram/qr | api_id, api_hash, при смене аккаунта password Cluster и code второго фактора, необязательный proxy |
POST telegram/qr/status | id попытки |
POST telegram/finish | id попытки, code Telegram либо password Telegram, необязательный replace_bot |
POST telegram/cancel | без параметров |
GET telegram/status | без параметров |
begin возвращает ID попытки, состояние code и тип доставки. finish при необходимости возвращает password; следующий запрос отправляет пароль Telegram. Успешное завершение возвращает connected и обновляет cookie. Номер, код и пароль не используются как принадлежность профиля: профиль определяется по действующему входу.
telegram/qr начинает вход без номера телефона. Ответ содержит id, stage="qr", ссылку url вида tg://login?token=… и expires, время окончания действия QR в секундах Unix. Рисунок QR строится на устройстве пользователя, ссылка сторонним службам не передаётся. Не записывай её в журналы и не сохраняй в общедоступном хранилище.
Пока QR показан, вызывай telegram/qr/status раз в две секунды. Истёкший QR обновляется при следующей проверке: ID попытки остаётся прежним, ссылка и срок меняются. После подтверждения в Telegram приходит stage="ready" либо stage="password", если нужен облачный пароль. Затем вызови telegram/finish с тем же ID: без кода для ready или с password Telegram для password. Только этот запрос сохраняет подключение и выдаёт обновлённую cookie.
Попытка целиком действует десять минут. Обновление самого QR этот срок не продлевает. Для отмены используй telegram/cancel. Новая попытка по номеру или QR закрывает предыдущую для того же юзербота. Создание QR ограничено пятью попытками за час отдельно от запросов кода; проверки QR ограничены 120 запросами за минуту. Проверки не расходуют попытки ввода пароля. Для qr и qr/status обязательны действующий вход, CSRF и права овнера.
Прокси задаётся объектом: proxy_type, addr, port, необязательные username, password, rdns.
Личный бот
| Метод | Параметры |
|---|---|
POST bot/attach | bot_token |
POST bot/detach | Без дополнительных параметров |
POST bot/managed | username, title |
GET bot/status | без параметров |
Для операций Telegram и личного бота можно передать unit другого юзербота, в котором действующий профиль является овнером. В GET он передаётся параметром запроса. В отсутствии unit выбирается собственный юзербот.
Сервисный бот настраивается оператором через закрытый интерфейс. Его токен не принимается публичным маршрутом управления службой. Обновления Telegram передаются отдельным POST в bot/update или bot/update/UNIT_ID, с X-Telegram-Bot-Api-Secret-Token. Для них не используется браузерная cookie или Origin.
Интерфейс модуля
Все операции используют POST, действующий вход, Origin и CSRF.
| POST | Параметры |
|---|---|
interface/files | id, instance, необязательные after и limit для доступных файлов |
interface/view | id юзербота, instance, необязательный lang |
interface/configure | id, instance, изменяемые values, текущая revision |
interface/aliases | id, instance, aliases, необязательные access и общий prefix |
interface/action | id, instance, action, values, уникальный request, при подтверждении confirmation |
interface/result | request из результата принятого действия |
access содержит ключи вида commands/ping, callbacks/add, actions/reset, handlers/watch и значения owner, trusted, public. В просмотре скрытые поля не выдаются. Новая версия настроек возвращается после сохранения.
Операция с подтверждением сначала возвращает confirmation и срок. Следующий запрос сохраняет те же аргументы и добавляет это значение. Принятое действие возвращает новый request для получения состояния: в очереди (queued), выполняется (running), завершено (done), ошибка (failed) или отменено (cancelled).
Подробнее об описании страниц и правах в MODULES.md.
Оператор
Операторский сокет предоставляет также bot/service, bot/webhook, auth/recover и subscription/grant. Через HTTP они недоступны. Секретные параметры передаются из закрытого файла или стандартного ввода, чтобы не оставлять пароль в истории оболочки:
.venv/bin/cluster --path ./data request bot/service - < /path/to/private/bot.jsonФайл находится вне проекта, имеет права 0600 и содержит объект с полем token. Для bot/webhook нужны url, случайный secret и id юзербота либо service. URL совпадает с соответствующим путём HTTP-службы.
Подробности профиля описаны в ACCOUNTS.md, подключения в TELEGRAM.md.
Полный список маршрутов и параметров есть в ROUTES.md. Управление данными и состоянием выполняется через portal/request: id, action, data. Овнер выбирает юзербота по id, сервер проверяет его полномочия. Дополнительный овнер выбирает юзербота параметром unit. Первое подключение Telegram и управление личным ботом не требуют повторного ввода данных входа. Смена уже привязанного Telegram-аккаунта подтверждается паролем Cluster и кодом двухэтапного входа.
Ошибки
Коды ошибок, сообщения исключений, письма и поле message системных уведомлений ядра на английском. Поле translation содержит тексты уведомления на русском и английском. Язык сайта выбирается отдельно. Пользовательские данные и переводы описания модуля сохраняются в исходном виде.
| Код | HTTP | Причина |
|---|---|---|
invalid_input | 400 | Неверные поля, типы или формат запроса |
invalid_state | 400 | Операция недоступна в текущем состоянии |
quota_exceeded | 400 | Не хватает пользовательской квоты |
access_denied | 403 | Вход или необходимые полномочия отсутствуют |
conflict | 409 | Версия настроек или ID запроса уже использованы иначе |
unavailable | 503 | Ядро, подключение или получатель недоступны |
not_found | 404 | HTTP-маршрут отсутствует |
method_not_allowed | 405 | Неверный метод HTTP |
Ошибка не содержит произвольный текст исключения модуля или секреты службы. Неудачный внешний вызов может возвращать отдельный kind, описанный в TELEGRAM.md. Неизвестный результат отправки не является подтверждением отсутствия действия.
Дополнительные состояния входа
Если пароль верен, но профиль использует двухэтапный вход, запрос без code возвращает second_factor_required с HTTP 403. Клиент показывает отдельное окно и повторяет вход с кодом. Неверный пароль по-прежнему возвращает access_denied; узнать о втором факторе до проверки пароля нельзя.
При регистрации сохраняются версия условий, время принятия, возрастная группа и подтверждение согласия законного представителя. Дату рождения запрашивать не нужно. Эти поля обязательны для новой регистрации, существующие профили не пересоздаются.
Метрики
POST portal/metrics с id доступен овнерам выбранного юзербота. samples содержит время, память в байтах, CPU в процентах одного ядра и скорости HTTP/WebSocket в байтах в секунду. null означает отсутствие замера. limits содержит пределы памяти и CPU, traffic содержит счётчики полезных данных запросов модулей через ядро. Трафик Telegram, заголовки и прямые соединения сюда не входят. Хранятся последние 120 замеров с интервалом от пяти секунд; история создаётся при запросах и сбрасывается после перезапуска ядра.
Терминал
terminal/list возвращает до 32 последних процессов выбранного юзербота без вывода. terminal/get принимает необязательный after, счётчик символов из предыдущего output_end. reset=true означает, что клиент должен очистить экран перед выводом: прежний курсор вышел за доступный хвост. Вывод ограничен последними 65536 символами. Управляющие последовательности ANSI обрабатывает экран терминала. terminal/resize принимает cols от 20 до 500 и rows от 5 до 200.
Административные действия
admin/profile возвращает сведения профиля и установленных модулей без секретов, а также действующую проверку данных, открытую текущим администратором. admin/user принимает start, stop, block или unblock. Блокировка отзывает сеансы и останавливает рабочие процессы; снятие блокировки автоматически их не запускает. Заблокировать текущего администратора этим методом нельзя. Для административных методов нужны назначенная роль, CSRF и включённый второй фактор, если для ID не задано отдельное серверное исключение.
admin/catalog возвращает публикации любых состояний. Аргументы search и state необязательны; значения фильтра: all, public, private, hidden, blocked, review. В записи есть author_profile, details, version_count, review_count и installations. admin/versions получает module и возвращает catalog, author и versions без путей файлов сервера.
admin/moderate принимает module, action и необязательный version. Действия verify, unverify, block_version, unblock_version, archive, unarchive требуют выбранную версию. Действия всей публикации hide, unhide, block, unblock, stop, remove передаются без версии. Снятие блокировки не отменяет скрытие публикации. unhide восстанавливает видимость с учётом последнего выбора автора при публикации. Если автор сделал модуль непубличным во время скрытия, снятие скрытия не делает его публичным.
admin/access открывает отдельную проверку только для чтения. Нужны id юзербота, reason, свой password, свой code и необязательный minutes от 5 до 120, по умолчанию 30. Ответ содержит ID проверки, администратора, причину и expires. Он не создаёт поддержку или овнерку.
admin/access/request принимает id, grant, action и data. grant должен совпадать с ID действующей проверки этого администратора и юзербота. Разрешены только logs/list, files/list, data/keys, data/get, configuration. Для первых четырёх параметры совпадают с операциями кабинета; configuration требует instance. Секретные поля конфигурации заменяются объектом {"set": true} или {"set": false}. Сведения о файлах не содержат путей и содержимого.
admin/access/close закрывает проверку по id и grant. admin/access/history получает id и возвращает до 100 последних событий. Журнал хранится 30 дней, результаты чтения в него не записываются. Для чтения Telegram-чатов, скачивания вложений, терминала и операций записи этот доступ не подходит. Доступ поддержки по разрешению пользователя остаётся отдельным механизмом. Порядок работы панели описан в MODERATION.md.
Пакеты и оформление публикаций
catalog/detail принимает module и возвращает карточку с описанием последней версии, автором, отзывами и числом установок. catalog/files принимает module, version и необязательные path, cursor: без пути возвращается список файлов, с путём текст или состояние binary/large. Закрытые исходники этим методом не выдаются.
catalog/diff принимает module, before, after и необязательные path, cursor. before=null означает сравнение с пустым пакетом. Без пути возвращается список изменённых файлов с status=added|deleted|modified; с путём возвращается diff выбранного файла. closed=true означает закрытые исходники, truncated=true сокращённый вывод текста. Список файлов выдаётся по 500 записей: передай next предыдущего ответа как cursor, пока next не станет null. total показывает число файлов в объединённом пакете; на отдельной странице сравнения может не оказаться изменений. before и after в ответе содержат сведения о версиях без серверных путей. Административные admin/files и admin/diff имеют те же параметры и работают после проверки роли и включённого двухэтапного входа, включая закрытые пакеты.
catalog/artwork принимает module, kind=avatar|banner и file, ID готового PNG в собственном файловом хранилище автора. file=null удаляет изображение. Ответ содержит kind, present, revision. catalog/image принимает module, kind и возвращает {data, mime} с base64 PNG либо null. Доступ соответствует видимости публикации. Для административного просмотра используется admin/image с теми же параметрами. Поле artwork карточки содержит контрольные суммы имеющихся изображений, а не их содержимое.
Профили и друзья
Все people/* требуют входа и CSRF. people/get принимает id, ID или логин профиля. Ответ содержит id, login, display_name, own, relationship, requests и разрешённые поля about, publications, friends. В visibility передаются результаты проверки видимости, настройки другого пользователя не раскрываются. Собственный ответ дополнительно содержит settings и verification (текущую заявку, статус галочки и последнее решение). Общие сведения профиля, поиск и списки друзей содержат verified и badge; чужие заявки и комментарии не выдаются.
people/update сохраняет display_name, about, visibility, requests. В visibility обязательны about, publications, friends, images, каждое со значением everyone, friends или hidden. requests включает приём заявок в друзья.
people/friends возвращает собственный список друзей и заявок. people/friend принимает id целевого профиля и action=request|accept|remove. Подтвердить заявку может только получатель, удаление доступно обоим участникам. Связь хранит from, to, state, created; состояния pending и accepted.
people/artwork принимает kind, file=None и изменяет собственное изображение. people/image принимает id, kind и проверяет видимость изображений до получения данных. Формат совпадает с оформлением публикаций. Подробности и ограничения в PROFILES.md.
В portal/dashboard каждый доступный овнеру юзербот содержит owner_profiles: ID, логин, Telegram ID и признак первоначального владельца. Для отзыва овнерки auth/owner может принимать target, ID профиля, вместе с remove=true, вместо telegram. Это позволяет отозвать доступ после отвязки Telegram. Добавление по-прежнему требует Telegram ID, пароль действующего овнера и при необходимости одноразовый код.
Подтверждение профиля
people/verification/request принимает category=developer|project|public|other, message и необязательный массив links. people/verification/cancel принимает ID собственной заявки в request. Одновременно возможна одна заявка в состоянии pending. Состояния: pending, approved, rejected, cancelled. Ограничения текста, ссылок и частоты приведены в профилях.
admin/verification/requests возвращает {requests, next}. Фильтр state принимает состояния заявки и all; по умолчанию pending. В after передаётся прежний next без изменений. За запрос просматривается до 200 записей и возвращается до 20 совпадений. Пустой список с непустым next означает, что просмотр можно продолжить.
admin/verification/review принимает request, decision=approve|reject и reason. При отказе reason обязателен. admin/verification/set принимает ID профиля в id, булево verified и необязательный reason; выдаёт или снимает галочку без заявки. Для этих операций нужны назначенная роль администратора, CSRF и включённый двухэтапный вход либо отдельное исключение для ID в настройках сервера. Поле admin_totp_required в portal/dashboard показывает, действует ли обязательное требование для текущего профиля. При выдаче галочки профиль должен быть активен.
verified нельзя записать через people/update. Проверка профиля не изменяет разрешения или галочки опубликованных модулей.
Получение изображений частями через people/image, catalog/image и admin/image описано в разделе профилей.
Автор публикации и отметки
catalog/list и catalog/detail возвращают author_profile: id, login, available, verified, badge. available=false означает, что страницу профиля открыть нельзя; удалённый профиль возвращает login=null. Поля почты, Telegram ID и служебные сведения сюда не входят. Отзывы дополнительно содержат такой же author_profile для своего автора; сведения получаются из текущего профиля, а не из описания пакета.
badge="founder" обозначает основателя Cluster. Для обычной галочки и отсутствия специальной отметки badge=null; наличие обычной галочки определяется verified. Поле badge доступно только для чтения. admin/verification/set управляет обычной галочкой и не меняет отметку основателя.
Управление публикациями автора
Все запросы требуют активного сеанса, CSRF и авторства. Овнерка в чужом юзерботе не даёт управления его публикациями.
| Запрос | Параметры | Результат |
|---|---|---|
POST catalog/list | own=true, необязательный search | Свои публикации, включая непубличные и ограниченные модератором |
POST catalog/manage | module | name, description, выбранный public, hidden, blocked, все testers, число действующих экземпляров installations, protected, protected_version |
POST catalog/update | module, title, description, public | true |
POST catalog/remove | module, confirmation с точным текущим названием | {deleted: true, retained: N} |
title ограничен 128 байтами UTF-8, description 8192 байтами. После удаления пробелов по краям оба значения должны быть непустыми. public должен иметь тип bool. При скрытии модератором выбор сохраняется в hidden_public, само скрытие и блокировка не снимаются. Карточка каталога использует отдельное description; details.manifest сохраняет исходное описание версии. Новые версии не сбрасывают отредактированные название и описание карточки.
У catalog/publish параметр public=null или пропущенный параметр сохраняет выбранную видимость существующей публикации; для новой публикации исходное значение true. Повторная отправка существующей версии не редактирует карточку.
catalog/remove закрывает чтение публикации, сравнение, новые установки, обновления и повторную публикацию под удалённым ID. Уже установленные экземпляры сохраняются. retained содержит их текущее число; пакеты и их квота освобождаются при обслуживании после удаления последнего экземпляра и окончания сборок. При ошибке очистка повторяется. Повторное удаление той же публикации автором безопасно и не возвращает квоту дважды. Автоматически устанавливаемый модуль защищён от удаления.
catalog/archive получает module, version, archived=true|false. Архивация не удаляет версии. catalog/check запрашивает проверку выбранной версии; архивированный или заблокированный выпуск не принимается. catalog/testers получает module, target с ID профиля или логином и remove=false|true. Для отзыва можно передать ID уже недоступного профиля. Галочка тестера на сайте не заменяет авторство и доступ к управлению.