Two branches: dev for everyday work, production for releases. - ci.yml builds and packs on dev and on pull requests; it never publishes. - release.yml runs on a push (usually a merge) into production: reads <Version> from the csproj, refuses to proceed if that tag already exists, publishes to nuget.org, and only then creates the tag and the Gitea release — so a tag always means a published package. - Drop --skip-duplicate: a duplicate push must fail loudly instead of reporting success while shipping nothing.
CertifiEd.Client
Клиентский SDK для CertifiEd — self-hosted платформы лицензирования on-prem ПО от Ofarandagon.
SDK проверяет лицензию локально по публичному ключу Ed25519: приложение работает без связи с сервером до 30 дней, а конфигурация (feature-флаги и лимиты) читается из самого подписанного токена — подделать нельзя.
Установка
dotnet add package CertifiEd.Client
Быстрый старт
using CertifiEd.Client;
await using var license = new CertifiEdLicenseClient(new CertifiEdClientOptions
{
ServerUrl = "https://api.licensing.example.com",
LicenseFilePath = "license.ced", // выданный клиенту файл
PublicKey = Convert.FromHexString(publicKeyHex), // 32 байта Ed25519
});
if (!await license.InitializeAsync())
{
Console.Error.WriteLine("Лицензия отсутствует, повреждена, подделана или истекла.");
return 1;
}
if (license.Status is not (CertifiEdLicenseStatus.Active or CertifiEdLicenseStatus.GracePeriod))
return 1;
if (license.HasFeature("reports.export"))
EnableReportExport();
var seats = license.GetConfig<int>("limits.maxSeats") ?? 1;
InitializeAsync загружает токен, офлайн проверяет подпись Ed25519 и срок, затем (best-effort) активируется на сервере и запускает фоновый heartbeat. Отсутствие сети не роняет приложение.
Публичный ключ выдаёт панель или API: GET /api/v1/client/public-key/{licenseKey} (hex → Convert.FromHexString). Ключ безопасно зашивать в приложение.
API
| Член | Назначение |
|---|---|
InitializeAsync(ct) |
Загрузка + офлайн-проверка подписи, активация, старт heartbeat. false — лицензия невалидна |
Status |
Active · GracePeriod · Expired · Invalid · Revoked |
HasFeature(name) |
Флаг из массива features подписанного конфига |
GetConfig<T>(path) |
Значение по dotted-path, напр. "limits.maxSeats" |
DisposeAsync() |
Best-effort деактивация (освобождает место) и остановка таймера. Идемпотентен |
Опции
| Опция | Описание |
|---|---|
ServerUrl |
Базовый URL сервера CertifiEd |
LicenseFilePath |
Путь к .ced (голый токен или скачанный JSON-конверт) |
PublicKey |
Сырые 32 байта публичного ключа Ed25519 |
HwFingerprint |
Переопределить отпечаток железа (по умолчанию вычисляется автоматически) |
Офлайн-режим
После каждого успешного heartbeat сервер возвращает подписанный маркер; SDK кэширует его рядом с файлом лицензии (license.ced.hb). Перезапуски без сети остаются лицензированными, пока маркер жив (до maxOfflineDays, максимум 30). Далее — GracePeriod, затем Expired.
Рекомендация: не блокируйте работу при первом же сбое сети — ориентируйтесь на Status, а не на факт ответа сервера.
Привязка к железу
Лицензия может быть привязана к машине по аппаратному отпечатку. Отпечаток вычисляется автоматически (HwFingerprint.Get()), при необходимости переопределяется через CertifiEdClientOptions.HwFingerprint — например, если у вас своя стабильная идентификация узла.
Если лицензия привязана к другой машине, активация вернёт ошибку несовпадения отпечатка. Перепривязка (замена оборудования) выполняется на стороне платформы оператором или администратором тенанта.
Обработка ошибок
InitializeAsyncвозвращаетfalse— файла нет, токен повреждён/подделан или срок истёк. Не бросает при недоступном сервере.- Сетевые сбои и таймауты внутри активации/heartbeat проглатываются осознанно — работает офлайн-окно.
- Проверяйте
Statusперед выдачей платной функциональности, а не только результатInitializeAsync.
Документация
Полная документация — в docs/ репозитория: протокол лицензирования, справочник API, webhooks и руководство по SDK. Рабочий пример — examples/CertifiEd.QuickStart.
© Ofarandagon, 2026. SDK распространяется по лицензии MIT.