Files
keysat/startos/actions/activateLicense.ts
T
Grant ca32309ad9 Add StartOS instructions.md; fix manifest links; clear retired-enforce-mode drift
- instructions.md: new, required for Start9 community-registry submission
- manifest: fix dead packageRepo and docsUrls links
- versions/v0.2.0.ts: drop stale 'NOT YET WIRED' header
- actions: remove retired enforce-mode references; showLicenseStatus no longer
  reads a nonexistent 'mode' field; relabel the Creator (free) tier
2026-06-13 06:40:11 -05:00

163 lines
5.2 KiB
TypeScript

// Action: activate the Keysat package's own self-license.
//
// The daemon embeds the keysat.xyz master public key at compile time
// (see licensing-service/src/license_self.rs). The operator pastes a
// LIC1-… key here; the daemon verifies it against the master pubkey,
// writes it to /data/keysat-license.txt, and swaps its runtime tier
// to Licensed without a restart.
//
// The daemon always boots regardless of license state (enforce mode was
// retired — see license_self.rs::check_at_boot). With no valid self-license
// it runs at the free Creator tier with Creator caps; this action records
// the license and lifts those caps without a restart.
import { sdk } from '../sdk'
import { store } from '../fileModels/store'
import { adminCall, LICENSING_URL } from '../utils'
const { InputSpec, Value } = sdk
const input = InputSpec.of({
license_key: Value.text({
name: 'License key',
description:
'Paste the LIC1-… license key issued for your Keysat install. ' +
'Buy or redeem one at registry.keysat.xyz.',
required: true,
default: null,
placeholder: 'LIC1-XXXXXXXXXXXX-XXXXXXXXXXXX',
}),
})
export const activateLicense = sdk.Action.withInput(
'activate-license',
async () => ({
name: 'Activate Keysat license',
description:
'Activate this Keysat install. Optional — Keysat runs at the free ' +
'Creator tier without it. Activating lifts the Creator caps, unlocks ' +
'recurring billing + Zaprite payments, and shows your tier in the admin UI.',
warning: null,
allowedStatuses: 'only-running',
group: 'License',
visibility: 'enabled',
}),
input,
async () => null,
async ({ effects: _effects, input: formInput }) => {
const storeData = await store.read().once()
if (!storeData) throw new Error('Store not initialized — restart the service.')
const key = formInput.license_key.trim()
if (!key) {
throw new Error('License key is required.')
}
const resp = await adminCall(
LICENSING_URL,
storeData.admin_api_key,
'/v1/admin/self-license',
{ method: 'POST', body: JSON.stringify({ license_key: key }) },
)
if (!resp.ok) {
const body = await resp.text()
let detail = body
try {
const parsed = JSON.parse(body)
if (parsed.detail) detail = parsed.detail
else if (parsed.error) detail = parsed.error
} catch (_) {}
throw new Error(`Activation rejected (HTTP ${resp.status}): ${detail}`)
}
const result = (await resp.json()) as {
ok: boolean
tier: {
tier: 'licensed' | 'unlicensed'
license_id?: string
product_id?: string
expires_at?: number
entitlements?: string[]
}
message: string
}
return {
version: '1',
title: 'Keysat license activated',
message:
result.message +
' The license is stored at /data/keysat-license.txt and survives upgrades and reinstalls (it is part of your StartOS backup set).',
result: null,
}
},
)
// Companion read-only action: surface the current self-license tier.
// Useful both as a sanity check after activation and as a way for the
// operator to see "am I running licensed or unlicensed?" without
// digging into logs.
export const showLicenseStatus = sdk.Action.withoutInput(
'show-license-status',
async () => ({
name: 'Show Keysat license status',
description:
'Reports whether this Keysat install is running licensed or unlicensed, ' +
'and which entitlements are active.',
warning: null,
allowedStatuses: 'only-running',
group: 'License',
visibility: 'enabled',
}),
async ({ effects: _effects }) => {
const storeData = await store.read().once()
if (!storeData) throw new Error('Store not initialized — restart the service.')
const resp = await adminCall(
LICENSING_URL,
storeData.admin_api_key,
'/v1/admin/self-license',
{ method: 'GET' },
)
if (!resp.ok) {
throw new Error(`Could not read license status: HTTP ${resp.status}${await resp.text()}`)
}
const j = (await resp.json()) as {
tier: 'licensed' | 'unlicensed'
license_id?: string
product_id?: string
expires_at?: number
entitlements?: string[]
reason?: string
}
if (j.tier === 'licensed') {
const exp = !j.expires_at
? 'perpetual'
: new Date(j.expires_at * 1000).toISOString().slice(0, 10)
const ents = (j.entitlements || []).length === 0 ? '(none)' : (j.entitlements || []).join(', ')
return {
version: '1',
title: 'Licensed',
message:
`License id: ${j.license_id}\n` +
`Expires: ${exp}\n` +
`Entitlements: ${ents}`,
result: null,
}
} else {
return {
version: '1',
title: 'Creator (free tier)',
message:
`This install is running at the free Creator tier.\n` +
`Reason: ${j.reason || 'no license configured'}\n\n` +
`Creator caps: 5 products, 5 policies per product, 10 active ` +
`discount codes. Activating a license lifts these caps and unlocks ` +
`recurring billing + Zaprite payments (the "Activate Keysat license" action).`,
result: null,
}
}
},
)