Премиум-аддоны и лицензирование
Эта страница показывает, как продавать ваше дополнение в официальном маркетплейсе Pano и заставить Pano убедиться, что оно работает только на серверах, которые действительно за него заплатили.
Продажа работает через точно тот же поток релиза, что и бесплатное дополнение, плюс две дополнительные части: встроенный во время сборки ключ лицензии в вашем jar (единственный скомпилированный файл, в котором отгружается ваше дополнение) и проверка лицензии во время выполнения в вашем коде. Иными словами: когда вы компилируете дополнение, вы включаете внутрь него небольшой файл ключа проверки (это часть времени сборки), а когда дополнение запускается, оно использует этот ключ, чтобы проверить, что сервер за него заплатил (это часть времени выполнения). Это полное прохождение, на которое указывает Сборка и публикация.
Если вы ещё не выпустили бесплатное дополнение, сначала прочтите Сборку и публикацию — всё там (релизная сборка, версионирование, каналы релизов, .releaserc.json, создание ресурса и API-токена) всё ещё применимо. Эта страница лишь добавляет премиум-слой поверх.
Платные ресурсы пока недоступны сторонним авторам
Премиум сейчас доступен только аккаунтам с доступом к панели на panomc.com — как и freemium, и для дополнений, и для тем. Продажа в маркетплейсе сторонним авторам пока не открыта, поэтому на шаге выбора цены оба варианта отключены, а API отклоняет их для всех остальных. Эта страница описывает, как работает премиум, чтобы вы могли планировать; пока программа не открыта, публикуйте дополнение бесплатным.
TL;DR — переход на премиум — это три небольших изменения
Большая часть этой страницы — фон. Реальная работа крошечная:
- Добавьте один флаг сборки (или одну переменную окружения CI), чтобы ваш jar собирался с ключом лицензии — Шаг 1.
- Сохраните существующую проверку лицензии boilerplate — два коротких метода, которые уже есть у свежего
pano-boilerplate-plugin— Шаг 2. - Опубликуйте как платный ресурс — выберите ценообразование paid и задайте
useGitHubLink: false— Шаг 3.
Затем протестируйте сами. Всё под Как это работает под капотом — необязательное чтение.
Никакая защита не абсолютна
Будьте честны с собой о том, что эта система может и не может делать: ни одна система лицензирования не может защитить код на 100%. Цель здесь — сделать несанкционированное использование как можно более трудным для подавляющего большинства пользователей, а не сделать его невозможным. Как и любое ПО, любой код, попадающий в руки конечного пользователя, по своей сути открыт: достаточно решительный и умелый человек всегда сможет разобрать его. Это верно для любого DRM, когда-либо созданного, не только для Pano. Оценивайте и поддерживайте ваше дополнение с учётом этой реальности.
Шаг 1 — Встройте ключ лицензии во время сборки
Премиум-сборка принимает несколько дополнительных входов, которые помещают публичный ключ проверки внутрь вашего jar. Это опции, которые вы передаёте при сборке — в командной строке или как переменные окружения в вашей автоматической сборке — а не то, что вы пишете в какой-либо файл в вашем репозитории (смотрите Конфигурация манифеста → Свойства премиум-сборки).
Если вы не передаёте ни одного из них, ваше дополнение собирается как обычный бесплатный jar, и проверка во время выполнения не делает ничего вообще («no-op») — так что тот же исходник может отгружаться бесплатным или премиум без изменений кода.
Вы никогда не помечаете дополнение «премиум» в файле конфигурации
Дополнение является премиум исключительно потому, что вы собрали его с ключом лицензии — ничего больше. Собрали с ключом → премиум; собрали без → бесплатное, и между ними нет изменений кода. Вы не редактируете никакой файл конфигурации, и нет поля premium нигде (ни в gradle.properties, ни в манифесте jar). (Темы работают иначе — они переключают "premium": true в manifest.json — но к дополнениям это не относится.)
Свойство Gradle — опция, которую вы добавляете к команде сборки, например ./gradlew build -PlicenseServer=prod. Переменная окружения — именованное значение, которое ваш CI (ваша автоматическая сборка, например на GitHub Actions) устанавливает перед запуском сборки. Вот все входы, которые вы можете использовать:
| Вход | Вид | Что делает |
|---|---|---|
-PlicenseServer=dev|prod|<url> | Свойство Gradle | Получает публичный ключ с сервера лицензий. Передайте одно из dev, prod или полный кастомный URL — dev → https://api-dev.panomc.com, prod → https://api.panomc.com. Рекомендуется — нечего секретного хранить. |
PANO_LICENSE_SERVER | Переменная окружения | То же, что -PlicenseServer, для CI (установите на шаге сборки Gradle). Используется, когда свойство не задано. |
-PpanoLicensePublicKey=<base64> | Свойство Gradle | Поставляет ключ напрямую (Base64 или PEM — два распространённых текстовых кодирования ключа; вы получили бы значение с panomc.com, и большинству эта опция никогда не нужна), пропуская любой сетевой вызов. |
PANO_LICENSE_PUBLIC_KEY | Переменная окружения | То же, что -PpanoLicensePublicKey, для CI. Используется, когда свойство не задано. |
Какой вход побеждает? Сборка использует первый из установленных:
-PpanoLicensePublicKey— ключ, который вы поставили напрямую в командной строкеPANO_LICENSE_PUBLIC_KEY— то же, из переменной окружения-PlicenseServer— получить ключ с сервера лицензийPANO_LICENSE_SERVER— то же, из переменной окружения
Если ни один из них не установлен, встроенный ключ пуст, и jar бесплатный.
Простейшая премиум-сборка:
./gradlew build -PlicenseServer=prodТеперь вы должны увидеть
jar в build/libs/, собранный с впечённым публичным ключом panomc.com. Нет отдельного флага «премиум» для установки или файла конфигурации, чтобы вглядываться — входы сборки, которые вы передали (Шаг 1), — это то, что решает, был ли встроен ключ проверки, а с ним и проверка лицензии. Надёжный способ подтвердить, что премиум-поведение реально работает, — прогнать его от начала до конца в Протестируйте сами.
Для CI (вашей автоматической сборки на GitHub Actions) установите PANO_LICENSE_SERVER на шаге сборки Gradle вашего workflow — например dev на ветке dev и prod на main — чтобы пуш производил корректно ключеванный jar. Стандартный workflow релиза pano-boilerplate-plugin не устанавливает его: его шаг сборки — обычный ./gradlew build, так что из коробки пуш производит бесплатный jar. Добавление этой переменной окружения на шаг сборки — единственное изменение, которое делает премиум-форк (gradle.properties из boilerplate явно об этом упоминает).
Конкретно это означает добавление блока env: на шаг сборки. Сниппет ниже иллюстративен — сопоставьте его с шагом сборки вашего собственного workflow:
# your existing release workflow, build step
- name: Build
env:
PANO_LICENSE_SERVER: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}
run: ./gradlew buildТеперь вы должны увидеть
Пуш в вашу ветку релиза теперь производит премиум jar вместо бесплатного (prod на main, dev на вашей dev-ветке).
Ключ публичный — выбор флага о удобстве, а не о секретности
Встроенное значение — это публичный ключ проверки panomc.com, так что скрывать нечего (это в отличие от вашего API-токена маркетплейса, который должен оставаться секретом). Предпочитайте -PlicenseServer / PANO_LICENSE_SERVER, чтобы сборка всегда получала текущий ключ; используйте -PpanoLicensePublicKey / PANO_LICENSE_PUBLIC_KEY только когда ваш CI не может достучаться до сервера лицензий во время сборки.
Всегда делайте чистую полную сборку для премиум-релиза
Как и для любого релизного jar, никогда не собирайте премиум-релиз с -Pnoui — этот флаг пропускает пересборку веб-UI дополнения, так что старая копия UI может оказаться внутри jar (смотрите выноску в Сборке и публикации). Флаг лицензии — отдельная забота: добавьте его к обычной, чистой ./gradlew build, а не к dev-сборке с -Pnoui.
Шаг 2 — Добавьте проверку лицензии во время выполнения
Проверка живёт в вашем классе плагина и использует PluginLicenseClient из boilerplate.
Если вы начали с boilerplate, этот код уже существует — ваша единственная задача подтвердить, что он всё ещё там. Свежий pano-boilerplate-plugin уже готов к лицензии и просто ведёт себя как бесплатный, пока вы не соберёте его с ключом (Шаг 1). Проверьте, что onStart() всё ещё начинается с licenseClient.requireValidLicense(). Если вы его удалили или добавляете премиум к существующему дополнению, добавьте следующее:
class ShoutboxPlugin : PanoPlugin() {
private val licenseClient by lazy { PluginLicenseClient(this) }
override suspend fun onStart() {
licenseClient.requireValidLicense() // no-op for free builds; throws if premium & invalid
// ... the rest of your startup
}
override suspend fun verifyLicense() {
licenseClient.requireValidLicense() // backs the panel's "Refresh license" button
}
}Два кусочка синтаксиса Kotlin в этом сниппете, на случай, если они для вас новы: by lazy { ... } просто означает «создать этот объект в первый раз, когда он используется, не раньше», а suspend помечает функцию, которую Pano выполняет как корутину (свой способ обработки конкурентности). Оба несущие — копируйте сигнатуры точно.
requireValidLicense()— это ворота. На премиум-jar она получает, проверяет и перекрёстно проверяет лицензию; на бесплатном jar (без встроенного ключа) она немедленно возвращается. Вызывайте её в началеonStart(), чтобы, если лицензия недействительна, брошенная ею ошибка немедленно останавливала ваше дополнение, до того как выполнится любой ваш код запуска.verifyLicense()— переопределяемый хукPanoPlugin(метод, который Pano предоставляет вам для заполнения), который кнопка Обновить лицензию панели вызывает после того, как хост получил свежий JWT — так что панель отражает реальный, текущий результат, а не устаревший. Вам не нужна здесь никакая кастомная логика — просто вызовите тот жеrequireValidLicense()внутри неё, как показано.
Когда requireValidLicense() терпит неудачу, она бросает LicenseRequiredException. PF4J (движок плагинов внутри Pano — ничего, с чем вы взаимодействуете напрямую) затем помечает только ваше дополнение как упавшее, а хост (платформа Pano, в которую установлено ваше дополнение) записывает причину; ядро Pano и все другие дополнения продолжают работать. Сохранение неломаемости платформы одним неправильно настроенным премиум-дополнением намеренно — оператор всё ещё может достучаться до своей панели, чтобы это решить.
Дополнительные проверки, усложняющие взлом: LicenseGuard (необязательно)
Глубокая защита просто означает не полагаться на одну-единственную проверку. Взломщик (тот, кто пытается пиратить ваше дополнение) может попытаться удалить единственный вызов requireValidLicense() в onStart. Чтобы усложнить это, boilerplate также поставляет LicenseGuard, который позволяет добавить однострочные перепроверки в код, который выполняется чаще всего — ваши API-эндпоинты (обработчики маршрутов), запланированные задачи, обработчики WebSocket. Эти перепроверки почти бесплатны, потому что переиспользуют уже полученную лицензию вместо обращения к panomc.com каждый раз:
override suspend fun handle(context: RoutingContext): Result {
LicenseGuard.assert(plugin)
// ... business logic
}Здесь plugin — ваш экземпляр PanoPlugin — передавайте ту переменную, которая его держит (например, эндпоинт, которому передали плагин при его создании — «внедрение через конструктор» — это просто то, что фреймворк передаёт плагин в конструктор эндпоинта за вас). Если лицензия недействительна, assert останавливает вызов так же, как основная проверка (бросая), вместо того чтобы пропустить запрос. LicenseGuard.assert(plugin) переиспользует кэшированную лицензию и перезапрашивает только если она истекла, так что стоимость ничтожна — но чем в большем числе мест она появляется, тем больше правок нужно сделать взломщику.
Этот шаг необязателен и может подождать, пока ваше дополнение реально не начнёт продаваться.
Шаг 3 — Опубликуйте как платный ресурс
Публикация премиум-дополнения использует те же три шага, что и бесплатного — создать ресурс, создать API-токен, позволить автоматизации загрузить версии — из Публикации в официальном маркетплейсе Pano. Две вещи отличаются:
1. Оцените как платное. На форме Create resource на panomc.com выберите опцию ценообразования paid вместо free. Как всегда для дополнений, ваш resourceId в маркетплейсе — точно ваш pluginId — смотрите подсказку про ID ресурса. (Темы получают случайно сгенерированный ID под названием UUID, но дополнения просто переиспользуют свой pluginId.)
2. Загрузите jar напрямую — не используйте useGitHubLink. Бесплатные дополнения задают useGitHubLink: true, чтобы избежать дублирующей загрузки. Премиум-дополнения должны позволить маркетплейсу держать официальную эталонную копию jar и записать её SHA-256 (её отпечаток — смотрите Как это работает под капотом), потому что этот записанный хеш — то, к чему привязывается лицензия каждого покупателя. В конфигурации плагина Pano в вашем .releaserc.json уберите useGitHubLink (или задайте его в false).
Блок ниже показан как diff: строка, начинающаяся с -, — та, что вы удаляете, строка, начинающаяся с +, — та, что вы добавляете, а всё остальное — включая плейсхолдер ..., который заменяет вашу существующую конфигурацию, оставленную без изменений — остаётся ровно как у вас уже есть:
["@PanoMC/semantic-release-pano", {
"file": "build/libs/pano-plugin-shoutbox-${version}.jar",
"panoVersion": "1.0.0",
- "useGitHubLink": true,
+ "useGitHubLink": false,
"repositoryUrl": "https://github.com/YourName/pano-plugin-shoutbox.git",
"configs": [ ... ]
}]Всё остальное в этой конфигурации — две записи каналов, tokenVar, branches — не изменяется относительно разбора .releaserc.json.
Что происходит, когда покупатель запускает его
Когда Pano покупателя запускает ваше премиум-дополнение, panomc.com выдаёт токен лицензии только если этот подключённый аккаунт купил ваш ресурс и SHA-256 работающего jar совпадает с хешем, записанным для этой версии. (Вы, как автор ресурса, всегда проходите эту проверку — что и позволяет вам тестировать своё собственное платное дополнение в Протестируйте сами.) Вот почему напрямую загруженный, записанный по хешу jar важен: это отпечаток, на котором держится вся проверка.
Что покупатели видят в своей панели
Если премиум-дополнение не может себя лицензировать, оператор не остаётся гадать — панель отражает это в трёх местах: значок статуса на каждое дополнение на Панель → Дополнения, карточка лицензии на странице деталей дополнения и баннер на дашборде. Частые статусы и их исправления:
| Статус | Значение | Исправление |
|---|---|---|
LICENSED | Действительная лицензия, дополнение работает. | Ничего делать не нужно. |
NO_PURCHASE | Аккаунт подключён, но не купил это дополнение. | Купите его на panomc.com (карточка даёт ссылку). |
NOT_CONNECTED | Нет аккаунта panomc.com, подключённого к этому Pano. | Подключите аккаунт в настройках панели. |
NETWORK_ERROR | panomc.com был недоступен во время проверки. | Восстановите соединение, затем нажмите Обновить. |
JAR_TAMPERED | Хеш работающего jar не совпадает с лицензированным. | Перескачайте дополнение с panomc.com. |
Нет конфигурации лицензирования со стороны Pano, которую оператору можно испортить — премиум-дополнения обрабатывают собственную проверку, а нелицензированное просто остаётся отключённым, пока остальной сайт работает.
Протестируйте сами
Прежде чем с кого-то брать деньги, прогоните весь цикл против вашего собственного аккаунта. Вы всегда проходите проверку покупки для вашего собственного ресурса, так что это предполагаемый способ проверить премиум-дополнение от начала до конца:
- Опубликуйте версию через ваш обычный поток релиза в предрелизный канал (например, ваш
dev/alpha-канал). Публикация — это то, что записывает SHA-256-хеш jar в маркетплейсе — без записанного хеша лицензии не к чему привязываться. - Получите ровно этот jar на локальный Pano. Либо соберите ту же версию с соответствующим сервером лицензий —
./gradlew build -PlicenseServer=devдля канала dev — либо скачайте опубликованный jar. Он должен быть побайтово той версией, чей хеш был записан на шаге 1. - Подключите ваш авторский аккаунт panomc.com к вашему локальному Pano в настройках панели (то же действие «подключить аккаунт», которое использует реальный покупатель).
- Запустите дополнение.
Теперь вы должны увидеть
Значок LICENSED на Панель → Дополнения и нормально работающее дополнение. Если вы вместо этого видите NOT_CONNECTED, завершите шаг 3; NO_PURCHASE на вашем собственном ресурсе обычно означает, что подключённый аккаунт — не ваш авторский аккаунт.
Опционально докажите, что проверка подделки работает: измените один байт в jar и запустите его снова — хеш больше не совпадает, и статус переключается на JAR_TAMPERED.
Как это работает под капотом
Всё ниже — фон. Вам это не нужно, чтобы отгрузить премиум-дополнение, но это объясняет, почему шагов выше достаточно.
Крипто-ликбез за 30 секунд
Pano использует асимметричную подпись. panomc.com держит секретный приватный ключ, который может создавать подписанные токены; все остальные получают соответствующий публичный ключ, который может только проверять эти подписи, но никогда их создавать. Вот почему безопасно впекать публичный ключ в ваш jar — он может проверять, но не может подделывать. RS256 — просто название этого алгоритма подписи.
Несколько терминов, которые использует эта страница:
- Публичный ключ / приватный ключ — пара выше. Публичный ключ может проверять; приватный ключ (хранимый panomc.com) может подписывать. Вот почему «публичный» ключ безопасно раздавать.
- JWT — небольшой подписанный текстовый токен, несущий claims (факты) вроде «этот сервер купил это дополнение». Любой с публичным ключом может проверить его, но никто без приватного ключа не может подделать. Подделать токен означает создать фальшивый, который всё ещё проходит проверку подписи — асимметричная подпись делает это практически невозможным.
- Хеш SHA-256 — отпечаток, вычисленный из точных байтов файла. Измените хоть один байт, и отпечаток меняется полностью.
- Хост — платформа Pano, в которую установлено ваше дополнение (самостоятельно размещённый сервер покупателя).
Как работает система лицензирования (простыми словами)
Премиум-дополнение несёт копию публичного ключа panomc.com, впечённую во время сборки. Оттуда:
- Когда Pano запускает ваше дополнение, дополнение просит хост получить недолговечный (1 час) подписанный токен лицензии — JWT — с panomc.com.
- Дополнение затем перепроверяет этот токен само, используя собственный встроенный публичный ключ — оно не доверяет хосту сделать это.
- Токен привязан к четырём вещам: этой конкретной установке Pano (идентифицированной через её подключённый аккаунт panomc.com), вашему ресурсу, версии дополнения и хешу SHA-256 работающего jar. Токен, выданный для одного сервера, версии или jar, бесполезен где-либо ещё.
- Если проверка проходит, дополнение стартует нормально. Если она не проходит, дополнение отказывается стартовать — но сам Pano продолжает работать, и сбой отражается в панели, чтобы оператор мог его исправить.
Важный момент дизайна, простыми словами: проверка, которая имеет значение, происходит внутри вашего дополнения, а не внутри Pano. Ядро Pano — открытый исходный код, его можно форкнуть или пропатчить, так что дополнение никогда не доверяет хосту проверку лицензии — оно перепроверяет подпись JWT само, своим собственным встроенным публичным ключом. Вот что означает «код вашего плагина — граница безопасности». Даже подделанный хост не может подделать токен, потому что у него нет приватного ключа panomc.com.
Что на самом деле проверяет requireValidLicense()
Вы никогда не вызываете ничего из этого сами — это то, что происходит внутри одного метода, который вы уже добавили на Шаге 2, перечислено здесь для любопытных. По порядку она:
- Немедленно возвращается, если дополнение не премиум (нет встроенного ключа).
- Просит хост (
getLicenseManager()) получить лицензию дляresourceId = pluginIdи встроенной версии. - Перепроверяет подпись JWT встроенным публичным ключом, против ожидаемого издателя — идентичности, от которой токен претендует происходить, то есть panomc.com (
getLicenseJwtIssuer()). Это реальная граница безопасности. - Подтверждает, что ресурс токена совпадает с вашим
pluginId, версия совпадает, а хеш jar SHA-256 совпадает с работающим jar (getOwnJarSha256()). - Подтверждает, что токен не истёк, затем кэширует его.
Отпечаток и привязка к версии
Для дополнений отпечаток лицензии — SHA-256 jar. Токен несёт этот хеш, а во время выполнения ваше дополнение сравнивает его с хешем jar, который оно реально запускает (getOwnJarSha256()). Если кто-то переупакует или пропатчит jar, хеш больше не совпадает с лицензированным, и проверка падает со статусом подделки.
Держите версии в движении через релизы
Лицензия выдаётся на версию + jar. Если вы патчите или редактируете вручную jar, который уже был выпущен, его хеш больше не совпадает с записанным для этой версии, так что он больше не будет лицензирован. Всегда отгружайте изменения как новый релиз через обычный поток; это единственный правильный путь. Свежий хеш на каждом релизе — также фича: он инвалидирует любую работу по взлому, проделанную против предыдущей сборки.
Заметки об усилении защиты
Система уже наслаивает несколько защит — привязка на установку/версию/jar, перепроверка подписи со стороны плагина, короткие 1-часовые токены и разбросанные вызовы LicenseGuard. Вы можете поднять планку ещё выше:
- Рассыпьте
LicenseGuard.assert(plugin)широко, чтобы ни одна удалённая проверка не отключала ворота. - Отгружайте каждое исправление как новый релиз, поскольку каждый новый хеш jar заставляет заново тратить усилия на взлом.
- Обфусцируйте ваш jar. Обфускация переименовывает ваши скомпилированные классы и методы в бессмысленные символы, так что взломщик не может легко найти код лицензии, чтобы удалить его. ProGuard — стандартный бесплатный инструмент. Это необязательно и может подождать, пока ваше дополнение реально не начнёт продаваться.
Но держите в голове предупреждение о честности в начале этой страницы: эти меры повышают стоимость взлома для подавляющего большинства пользователей; они не делают его невозможным.
Куда дальше
- Сборка и публикация — релизная сборка, версионирование и полный поток публикации в маркетплейс, на котором строится эта страница.
- Конфигурация манифеста — свойства премиум-сборки в контексте остальной части
gradle.properties. - Разработка бэкенда — где живут ваш класс
PanoPlugin, эндпоинты и жизненный циклonStart. - Темы используют аналогичную схему отпечатка всего zip — смотрите премиум-темы.