usulnet Licensing & Editions

February 16, 2026 ยท View on GitHub

This document defines the three usulnet editions, their feature sets, resource limits, and how the license system works at a technical level.


Editions Overview

EditionPrice ModelTarget
Community (CE)Free (AGPLv3)Homelab, personal projects, small teams
BusinessPer-node subscriptionSMBs, growing teams, multi-node clusters
EnterpriseCustom pricingLarge organizations, compliance, unlimited

Feature Matrix

Core Features (all editions)

FeatureCEBusinessEnterprise
Container management (create, start, stop, remove)YesYesYes
Image management (pull, build, tag, push)YesYesYes
Volume & network managementYesYesYes
Stack/Compose deploymentYesYesYes
Basic dashboard & metricsYesYesYes
Single-node Docker managementYesYesYes
Web terminal (single session)YesYesYes
Basic RBAC (admin/user)YesYesYes
Webhook notifications (1 channel)YesYesYes

Business Features

FeatureCodeCEBusinessEnterprise
Custom Rolescustom_roles-YesYes
OAuth Authenticationoauth-YesYes
LDAP/Active Directoryldap-YesYes
Multi-channel Notificationsmulti_notification-YesYes
Audit Log Exportaudit_export-YesYes
Multiple Backup Destinationsmulti_backup-YesYes
API Keysapi_keys-YesYes
Priority Supportpriority_support-YesYes
Docker Swarm Managementswarm-YesYes
Git Sync (GitOps)git_sync-YesYes

Enterprise-Only Features

FeatureCodeCEBusinessEnterprise
SSO/SAMLsso_saml--Yes
High Availability Modeha_mode--Yes
Shared Terminalsshared_terminals--Yes
White-Label Brandingwhite_label--Yes
Compliance (SOC2, HIPAA)compliance--Yes
OPA Policy Engineopa_policies--Yes
Image Signing & Verificationimage_signing--Yes
Runtime Securityruntime_security--Yes
Log Aggregationlog_aggregation--Yes
Custom Dashboardscustom_dashboards--Yes
Ephemeral Environmentsephemeral_envs--Yes
Manifest Buildermanifest_builder--Yes

Resource Limits

ResourceCEBusinessEnterprise
Max Nodes1 (local only)From license + 1Unlimited
Max Users3From licenseUnlimited
Max Teams15Unlimited
Max Custom Roles1UnlimitedUnlimited
Max LDAP Servers13Unlimited
Max OAuth ProvidersDisabled3Unlimited
Max API Keys325Unlimited
Max Git Connections15Unlimited
Max S3 Connections15Unlimited
Max Backup Destinations15Unlimited
Max Notification Channels1UnlimitedUnlimited

Note: A limit value of 0 in the code means "unlimited". For CE, LDAP is capped at 1 server and OAuth is disabled entirely (0 = disabled, gated by the FeatureLDAP / FeatureOAuth feature flags).

Business Node Counting

Business licenses specify purchased nodes in the JWT (nod claim). The total allowed nodes are calculated as:

total_nodes = purchased_nodes + CEBaseNodes (1)

So a customer who buys 3 nodes gets 4 total (3 purchased + 1 base/master).


License Keys (JWT)

License keys are JSON Web Tokens (JWT) signed with RSA-4096 (RS512).

Token Structure

{
  "lid": "USN-xxxx-xxxx",
  "eml": "<sha256 of customer email>",
  "edition": "biz",
  "nod": 3,
  "usr": 15,
  "features": ["custom_roles", "oauth", "ldap", ...],
  "exp": 1735689600,
  "iat": 1704067200
}
FieldDescription
lidLicense ID (must start with USN-)
emlSHA-256 hash of the customer email
editionbiz (Business) or ee (Enterprise)
nodPurchased node count (Business only)
usrAllowed user count (Business only)
featuresArray of enabled feature flags
expExpiration timestamp (required)
iatIssued-at timestamp (required)

Security

  • Algorithm: Only RS512 is accepted (alg=none and HS256 confusion attacks are rejected)
  • Key size: RSA-4096 minimum enforced
  • Public key: Embedded in the binary (internal/license/keys/public.pem)
  • Private key: Only exists on the Cloudflare Worker that issues licenses
  • Instance binding: Licenses are tied to an instance fingerprint (machine ID + hostname + salt)

License Lifecycle

Activation

  1. Admin submits license key via POST /api/v1/license
  2. JWT is cryptographically verified (RS512 signature, expiration, claims)
  3. On success: license state is applied in-memory and persisted to disk
  4. Audit log records the activation with edition, license ID, and user info

Deactivation

  1. Admin calls DELETE /api/v1/license
  2. License is removed from memory and disk
  3. System reverts to Community Edition
  4. Audit log records the deactivation

Background Revalidation

A background goroutine re-validates the stored license every 6 hours:

  • If the license has expired, info.Valid is set to false
  • The edition marker is preserved so the UI shows "expired" rather than "CE"
  • Features are disabled but data is preserved

Expiration Notifications

The system sends notifications at configurable thresholds before expiration:

Days RemainingNotification TypePriority
30license_expiryHigh
15license_expiryHigh
7license_expiryHigh
3license_expiryHigh
1license_expiryHigh
0 (expired)license_expiredCritical

Duplicate notifications are suppressed with a 24-hour cooldown per threshold.


Graceful Degradation

When a paid license expires, the system does not crash or lock out users. Instead, it gracefully degrades:

  1. Features: All paid features are disabled (return HTTP 402)
  2. Limits: Resource limits revert to Community Edition values
  3. Data: All existing data is preserved and accessible
  4. Status endpoint: GET /api/v1/license/status reports degradation state
  5. UI: Shows "License expired" banner with renewal instructions

Degradation States

StateEdition ShownLimits AppliedFeatures
Active licenseBusiness/EnterpriseLicense limitsAll licensed features
Expired licenseBusiness/Enterprise (expired)CE limitsNone (402 on access)
No licenseCommunity EditionCE limitsNone

API Endpoints

MethodPathDescriptionAuth
GET/api/v1/licenseGet current license infoAdmin
POST/api/v1/licenseActivate license keyAdmin
DELETE/api/v1/licenseDeactivate licenseAdmin
GET/api/v1/license/statusDetailed status with degradationAdmin

Middleware Enforcement

The license system provides several middleware functions for route protection:

MiddlewareBehaviorHTTP Status
RequireFeature(feature)Blocks if feature not in license402
RequirePaid()Blocks CE and expired licenses402
RequireEnterprise()Blocks non-Enterprise editions402
RequireValidLicense()Blocks expired licenses402
RequireLimit(resource, currentFn, limitFn)Blocks when resource limit reached402

All license-related denials return HTTP 402 Payment Required with a JSON error body containing the error code (LICENSE_REQUIRED, LICENSE_EXPIRED, or LIMIT_EXCEEDED) and upgrade guidance.


Implementation Files

FilePurpose
internal/license/license.goEditions, features, limits, Info struct
internal/license/validator.goJWT parsing and verification
internal/license/provider.goRuntime state management
internal/license/store.goDisk persistence
internal/license/fingerprint.goInstance identification
internal/license/expiration.goExpiration checker and graceful degradation
internal/license/notification_adapter.goBridge to notification service
internal/api/handlers/license.goREST API endpoints
internal/api/middleware/license.goHTTP middleware functions