Repository navigation
feat(license): cloud license auto-import and device write-off - #246
Merged
Merged
Conversation
Import a license from OpenCSG in Settings, write the device off against the license, and cap each license at 10 devices when online. Offline imports keep working and report the device after the next start or sign-in. - add GET /api/license/cloud and POST /api/license/cloud/import - record write-off state in license.activation and report it on start/sign-in - derive a stable device id from the machine identifier, with a storage-root fallback - keep older servers working through the offline path with a 24h retry - surface cloud subscription status and device usage in Settings and the CLI Fixes #240.
The cloud status reports has_license=false for an expired license, so the disabled import button explained it as "no active subscription" even when the license row exists. Check the state first and reuse the expired hint.
ganisback
reviewed
Nov 11, 2026
CSGHUB_LITE_LICENSE is read-only, so startup and sign-in reporting must skip it: no activation record and no activation request. Containers and CI would otherwise consume a permanent seat on every start. Retarget the raw-PEM test at a file-managed license and add a test that an env-managed license sends nothing.
ganisback
approved these changes
Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
License import now performs a device write-off. A license is limited to 10 devices; when online, an import first asks the server for a seat and is rejected if the limit is reached. Offline import still works: it only records the device locally and reports it once the machine is back online. The settings page gains cloud subscription status and a "Import from OpenCSG" button.
Closes #240
Server-side change: product/starhub/starhub-server!3356 (
GET /api/v1/licenses/csglite/status,POST /api/v1/licenses/csglite/activations)User-visible changes
used / 10. Device usage replaces the old Seats row.LICENSE-ERR-7with "This license has exceeded its device limit; new devices cannot be imported", and no license is installed.pending, and it is reported on the next start or successful sign-in. After a successful report the record becomesreportedand is never re-sent.csghub-lite license installfollows the same flow and notes that the device will be synced later when the install was offline.Local HTTP API changes
GET /api/license/cloud: always 200, returnslogged_in,online,supported,state,has_license,not_started,license_key,expire_time,grace_until,subscription_status,used,limit,env_managed, andreason. Cached for 10 seconds.POST /api/license/cloud/import: download, verify, write off, and install. Error codes:login_required,token_invalid,no_license,already_current,cloud_not_started,env_managed,rate_limited,bad_request,unreachable,unsupported.PUT /api/licenseresponses gainoffline_import(import responses only) so the UI can say the device will be synced later.openapi/local-api.jsonandinternal/server/static/openapi/local-api.jsonare updated.Invariants
license.activationis a separate sidecar state file.CSGHUB_LITE_LICENSEis read-only: it is not auto-imported, overwritten, or written off.Non-goals
Compatibility
supported=false, auto-import unavailable; manual import installs offline withpending+ 24h backoffRisks
Verification
Run locally on macOS arm64:
gofmt -l internal pkg cmd: no formatting issues in the changed filesgo build ./...: passesgo test ./internal/deviceid ./internal/csghub ./internal/license ./internal/server ./internal/cli: passescd web && npx tsc --noEmit: passescd web && npx vitest run: 23 files, 99 tests passEnd-to-end against real processes (server and Lite both running locally):
main) + new Lite:supported=false, manual import usespending+ 24h, no duplicate report after restartCSGHUB_LITE_LICENSE): startup and sign-in reporting send no request and write no activation recordNot covered: Windows/Linux on real hardware, desktop-app UI verification.
Breaking changes
None. New endpoints and response fields only; existing endpoint semantics are unchanged.
LicenseState.Seatswas renamed toDevices(internal type and web UI only, not externally exposed).