chore: extract the SDK into its own repository
CI / build (push) Successful in 33s
CI / publish (push) Successful in 32s

Standalone home for the CertifiEd .NET client SDK, split out of the
platform repository with its history.

- src/ layout with its own solution, Directory.Build.props and
  global.json (warnings as errors, XML docs required).
- Gitea Actions CI: build and pack on every push, publish to nuget.org
  on a v* tag using the NUGET_API_KEY secret.
- CONTRIBUTING documents the release flow and the one-time CI setup.
This commit is contained in:
Faris Laptop
2026-07-20 15:36:26 +05:00
parent e0354458df
commit 2cd024f782
11 changed files with 248 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
name: CI
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Restore
run: dotnet restore
# Directory.Build.props turns warnings into errors, so this also gates style.
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Pack
run: dotnet pack src/CertifiEd.Client/CertifiEd.Client.csproj --configuration Release --no-build --output artifacts
- name: Upload package artifacts
uses: actions/upload-artifact@v3
with:
name: nupkg
path: artifacts/*
publish:
# Tag pushes only: v1.2.3 publishes version 1.2.3 to nuget.org.
needs: build
if: startsWith(gitea.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Derive version from tag
id: version
run: echo "value=${GITEA_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
env:
GITEA_REF: ${{ gitea.ref }}
- name: Pack
run: >
dotnet pack src/CertifiEd.Client/CertifiEd.Client.csproj
--configuration Release
-p:Version=${{ steps.version.outputs.value }}
--output artifacts
# NUGET_API_KEY must be added as a repository secret. Without it the step
# fails loudly rather than silently shipping nothing.
- name: Push to nuget.org
run: >
dotnet nuget push "artifacts/*.nupkg"
--source https://api.nuget.org/v3/index.json
--api-key "$NUGET_API_KEY"
--skip-duplicate
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+8
View File
@@ -0,0 +1,8 @@
bin/
obj/
*.user
.vs/
.idea/
artifacts/
*.nupkg
*.snupkg
+32
View File
@@ -0,0 +1,32 @@
# Разработка и релиз
## Сборка
```bash
dotnet build
dotnet pack src/CertifiEd.Client/CertifiEd.Client.csproj -c Release -o artifacts
```
Требуется .NET 10 SDK (см. `global.json`). Предупреждения компилятора трактуются как ошибки, публичные члены обязаны иметь XML-документацию.
## Релиз
Версия пакета берётся из тега:
```bash
# 1. Обновить <Version> в src/CertifiEd.Client/CertifiEd.Client.csproj
# 2. Закоммитить и поставить тег
git tag v1.0.1
git push origin main --tags
```
CI (`.gitea/workflows/ci.yml`) на теге `v*` собирает пакет с версией из тега и публикует его на nuget.org.
### Что нужно один раз настроить
- В настройках репозитория → Actions → Secrets добавить **`NUGET_API_KEY`** — ключ nuget.org с правом публикации пакета `CertifiEd.Client`.
- Убедиться, что к репозиторию подключён Actions-раннер (Settings → Actions → Runners). Без раннера workflow не запустится.
## Совместимость
Публичный API следует semver: ломающие изменения — только в мажорной версии. Формат токена лицензии и протокол активации описаны в документации платформы CertifiEd.
+5
View File
@@ -0,0 +1,5 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/CertifiEd.Client/CertifiEd.Client.csproj" />
</Folder>
</Solution>
+19
View File
@@ -0,0 +1,19 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
<!-- Reproducible builds on CI. -->
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true' or '$(CI)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Ofarandagon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+87
View File
@@ -0,0 +1,87 @@
# CertifiEd.Client
Клиентский SDK для [CertifiEd](https://git.ofarandagon.tj/Ofarandagon/CertifiEd) — self-hosted платформы лицензирования on-prem ПО от [Ofarandagon](https://ofarandagon.tj).
SDK проверяет лицензию **локально** по публичному ключу Ed25519: приложение работает без связи с сервером до 30 дней, а конфигурация (feature-флаги и лимиты) читается из самого подписанного токена — подделать нельзя.
## Установка
```bash
dotnet add package CertifiEd.Client
```
## Быстрый старт
```csharp
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/`](https://git.ofarandagon.tj/Ofarandagon/CertifiEd) репозитория: протокол лицензирования, справочник API, webhooks и руководство по SDK. Рабочий пример — `examples/CertifiEd.QuickStart`.
---
© Ofarandagon, 2026. SDK распространяется по лицензии MIT.