Add 'Set Recap License' StartOS action + s/Keysat license/Recap license/
Two related changes:
1. New StartOS action: 'Set Recap License'
Symmetric with the existing 'Set Gemini API Key' action — paste a
LIC1-... key into the StartOS Actions menu and it gets persisted.
Added because some users prefer the StartOS form for credentials
over the in-app activation modal.
Implementation:
• startos/file-models/config.json.ts: schema gains recap_license_key
• startos/actions/setLicense.ts: input form (masked, regex-checks
for the LIC1- prefix), persists via configFile.merge()
• startos/actions/index.ts: registers the new action
• server/license.js: readLicenseString() falls back to
startos-config.json after the legacy license.txt path. Resolution
order: env → license.txt → startos-config.json
• server/license-middleware.js: faster license-file poll (30 s,
env-overridable RECAP_LICENSE_FILE_POLL_MS) re-runs checkLicense
so action-set keys take effect within seconds, not the 6 h online
cycle. If the new key parses as 'licensed', kicks an immediate
online check to confirm.
2. Copy fix: 'Keysat license' → 'Recap license' in user-facing text
Keysat is the licensing system underneath, but customers buy a
'Recap license'. Updated:
• Activation screen subtitle (public/index.html)
• 402 message in the activation gate (server/license-middleware.js)
Internal references (PRODUCT_SLUG, KEYSAT_BASE_URL, the issuer.pub
filename, the 'Issuer: licensing.keysat.xyz' display in the
activation card) stay as Keysat — those are accurate.
Smoke tested locally: starting the server with no license, then
writing a fake LIC1-... key into startos-config.json, the
license-file poll picks it up within ~2 s and transitions state from
'unlicensed' to 'invalid' (since the fake key fails Ed25519
verification, as expected). With a real key, the same path would land
in 'licensed'.
This commit is contained in:
@@ -30,6 +30,12 @@ const VALIDATE_INTERVAL_MS = parseInt(
|
||||
10
|
||||
);
|
||||
const ACTIVATE_VALIDATE_TIMEOUT_MS = 8000;
|
||||
// How often to re-read the license file (fast path for keys set via the
|
||||
// StartOS action — the 6 h online cycle is too slow for that UX).
|
||||
const LICENSE_FILE_POLL_MS = parseInt(
|
||||
process.env.RECAP_LICENSE_FILE_POLL_MS || "30000",
|
||||
10
|
||||
);
|
||||
|
||||
// ── Online refresh ──────────────────────────────────────────────────────────
|
||||
// Calls the licensing server (with the network-error grace logic in
|
||||
@@ -59,6 +65,28 @@ export function startLicenseRefresh() {
|
||||
setInterval(() => {
|
||||
refreshLicenseOnline("scheduled").catch(() => {});
|
||||
}, VALIDATE_INTERVAL_MS);
|
||||
// Faster offline-only re-read so a license set via the "Set Recap
|
||||
// License" StartOS action (or a manual edit to license.txt) is picked
|
||||
// up within seconds instead of 6 h. Calls checkLicense() rather than
|
||||
// validateOnline() to avoid hammering Keysat — the next scheduled
|
||||
// validateOnline tick will confirm with the server. If a fresh key
|
||||
// appears, kick an immediate online check too so an unrevoked Pro
|
||||
// license doesn't get stuck pending until the 6 h tick.
|
||||
setInterval(() => {
|
||||
const prev = LIC;
|
||||
const next = license.checkLicense();
|
||||
if (next.licenseId !== prev.licenseId || next.state !== prev.state) {
|
||||
LIC = next;
|
||||
console.log(
|
||||
`[license] file refresh: state=${LIC.state}` +
|
||||
(LIC.reason ? ` reason=${LIC.reason}` : "") +
|
||||
` entitlements=[${[...LIC.entitlements].join(",")}]`
|
||||
);
|
||||
if (next.state === "licensed") {
|
||||
refreshLicenseOnline("file change").catch(() => {});
|
||||
}
|
||||
}
|
||||
}, LICENSE_FILE_POLL_MS);
|
||||
}
|
||||
|
||||
// ── Free-tier slot management ───────────────────────────────────────────────
|
||||
@@ -139,7 +167,7 @@ export function setupLicenseMiddleware(app) {
|
||||
message:
|
||||
LIC.state === "licensed"
|
||||
? "Your license is missing the 'core' entitlement. Contact the seller."
|
||||
: "This feature requires a Keysat license. Upgrade to unlock.",
|
||||
: "This feature requires a Recap license. Upgrade to unlock.",
|
||||
state: LIC.state,
|
||||
reason: LIC.reason,
|
||||
activate_url: "/#activate",
|
||||
|
||||
Reference in New Issue
Block a user