# Сервер лицензирования и обновлений bj — контракт API > Сервер лицензий живёт в отдельном репозитории **zuevav/lic_Bridge-and-Join-s** (наша инфраструктура, клиентам не поставляется). Здесь — контракт API для клиентской части bj (`internal/lkgateway/licenseclient.go`). Формат токена общий: `internal/license`, тест `TestTokenCompat` одинаков в обоих репозиториях. Сервер: **https://lic.zetit.ru** (ВМ 46.173.19.220, Ubuntu 26.04). Адрес зашит в bj (`internal/lkgateway`, константа), в интерфейсе не меняется. С этого же сервера bj получает обновления. ## Модель - Лицензия — прежний токен `internal/license`: подписанный Ed25519 JSON `License`, строка `base64(payload).base64(sig).keyid`. Ключ подписи — `~/keys/license.priv` (keyid `main`); открытый ключ зашит в bj. - Клиенту выдаётся **код активации** (`BJ-XXXX-XXXX-XXXX`), один на лицензию; им активируется до `max_nodes` установок (0 — без лимита). - При активации сервер выдаёт токен **на 60 дней** (`expires_at`), но не дольше `contract_until`; в токене `install_id` установки и `contract_until` договора. - bj продлевает токен **раз в сутки**. Отзыв = сервер больше не продлевает; уже выданный токен доживает свой срок (запас на недоступность сервера). - Офлайн-ключи (`bj-license issue`, без `install_id`) продолжают работать — для закрытых контуров без интернета. ## Идентификатор установки `install_id` — UUID v4, создаётся bj при первом запуске и хранится в `/var/lib/bj/.bj/setup.json` (`license.install_id`). Вместе с ним bj передаёт `machine` — sha256(`/etc/machine-id`) в hex (для учёта и обнаружения клонов, не для отказа). ## Запросы Все тела — JSON, `Content-Type: application/json`. Ошибки — HTTP 4xx/5xx и `{"error": "<код>", "message": "<текст по-русски>"}`. ### POST /v1/activate ```json {"code": "BJ-7K3M-Q9XP-2HDA", "install_id": "…uuid…", "machine": "…sha256…", "hostname": "bj-server-01", "version": "0.1.0"} ``` 200: ```json {"token": "", "license_id": "…uuid…", "tenant": "ИК Фонтвьель", "expires_at": "2026-11-14T10:00:00Z", "contract_until": "2027-09-14T00:00:00Z", "renew_after_sec": 86400} ``` Коды ошибок: `bad_request`, `unknown_code`, `revoked`, `contract_expired`, `max_nodes` (лимит установок исчерпан). Повторная активация той же `install_id` — не новая установка, просто новый токен. ### POST /v1/renew ```json {"token": "<текущий токен>", "install_id": "…", "machine": "…", "hostname": "…", "version": "…"} ``` Сервер проверяет подпись токена (срок **не** проверяет — продлить можно и истёкший), совпадение `install_id` с токеном, что установка известна и лицензия не отозвана. 200 — тот же формат, что у activate. Ошибки: `bad_token`, `install_mismatch`, `unknown_install`, `revoked`, `contract_expired`. Каждый успешный renew обновляет `last_seen`, `version`, `hostname`, IP установки. ### Обновления - `GET /v1/{channel}/manifest.json` — подписанный манифест (`internal/release`), без авторизации. - `GET /v1/{channel}/files/{file}` — только с заголовком `Authorization: License `: подпись верна, срок не истёк, лицензия не отозвана, в признаках есть `updates` (или план `enterprise`). Иначе 401/403 с JSON-ошибкой. Ключ подписи манифестов отдельный от лицензионного. ### GET /healthz `ok`. ## Кабинет администратора `https://lic.zetit.ru/admin/` — вход по логину и паролю (bcrypt), только для нас: клиенты (название, ИНН), лицензии (план, признаки, max_nodes, contract_until, код активации, отзыв/восстановление), установки (install_id, hostname, версия, IP, последнее продление, срок токена), журнал событий, выпуск офлайн-ключа для закрытого контура.