Initial public commit

This commit is contained in:
Keysat
2026-05-07 10:41:57 -05:00
commit 8c9bc75e24
12 changed files with 906 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
// -----------------------------------------------------------------------------
// Licensing template: CONFIGURATION — FILL IN THE THREE VALUES BELOW.
// -----------------------------------------------------------------------------
//
// This is the one file you must edit to adopt the licensing template. The
// action files consume these constants; nothing else should need changes.
//
// After a buyer (Bob) installs your Start9 package, the "Activate license" and
// "Buy license" actions in his package dashboard will talk to YOUR licensing
// server (the `BASE_URL` below), paid in Bitcoin via your BTCPay.
// -----------------------------------------------------------------------------
/**
* The public URL of YOUR running licensing-service, as reachable from the
* buyer's Start9 server. Typically a Tor .onion, a clearnet domain, or both
* (see TOR_FALLBACK below).
*
* Example: 'https://license.example.com'
*/
export const LICENSING_BASE_URL = 'https://license.YOUR-DOMAIN.example'
/**
* Optional Tor fallback. If set, the actions will try this URL if the primary
* URL fails — useful when your licensing server is behind Tor and the buyer's
* clearnet is flaky.
*
* Example: 'http://abc...xyz.onion'
*/
export const LICENSING_BASE_URL_TOR: string | null = null
/**
* The product slug you configured in your licensing-service (via the
* "Create product" action or the admin API). This is how the service knows
* which product a license is for.
*
* Example: 'bitcoin-ticker-pro'
*/
export const PRODUCT_SLUG = 'your-product-slug'
/**
* Your licensing-service's Ed25519 public key in PEM form. Embed the full
* multi-line block as a template literal. The buyer's device verifies license
* keys AGAINST THIS KEY offline — no network required for the common case.
*
* Get this from your licensing-service: run the "Show admin credentials"
* action on your own Start9, or hit GET /v1/pubkey.
*
* Example:
* -----BEGIN PUBLIC KEY-----
* MCowBQYDK2VwAyEA...
* -----END PUBLIC KEY-----
*/
export const ISSUER_PUBKEY_PEM = `-----BEGIN PUBLIC KEY-----
PASTE YOUR ISSUER PUBLIC KEY PEM HERE
-----END PUBLIC KEY-----`
/**
* The product name shown in UI prompts. Purely cosmetic.
*/
export const PRODUCT_DISPLAY_NAME = 'Your Product'
+47
View File
@@ -0,0 +1,47 @@
// Runtime helper: "Is this install licensed?" — call from `main.ts` before
// you start your app's main process, so the app can refuse to boot (or boot
// in a reduced-functionality trial mode) when no valid key is present.
//
// This never touches the network — only the signature. The offline check is
// what you want at startup: nothing on the internet should be able to keep
// the buyer's app from starting if the key is legitimate.
import { Verifier, PublicKey, type VerifyOk } from '@keysat/licensing-client'
import { ISSUER_PUBKEY_PEM, PRODUCT_SLUG } from './config'
export interface LicenseGateResult {
licensed: boolean
/** Populated if licensed === true. */
details?: VerifyOk
/** Populated if licensed === false. Human-readable. */
reason?: string
}
/**
* Check the license key stored in the package store. Pure offline — runs in
* a few milliseconds.
*
* Typical usage in main.ts:
*
* const gate = checkLicenseGate(await sdk.store.getOwn(effects, sdk.StorePath).const())
* if (!gate.licensed) {
* // options: exit, or inject a "trial mode" env var into your daemon
* }
*/
export function checkLicenseGate(store: {
license_key?: string | null
}): LicenseGateResult {
const key = store.license_key
if (!key) return { licensed: false, reason: 'no license key stored' }
try {
const ok = new Verifier(PublicKey.fromPem(ISSUER_PUBKEY_PEM)).verify(key)
return { licensed: true, details: ok }
} catch (e) {
return {
licensed: false,
reason: e instanceof Error ? e.message : 'verification failed',
}
}
}
export { PRODUCT_SLUG }
+31
View File
@@ -0,0 +1,31 @@
// Store shape additions for the licensing template.
//
// Merge these fields into your package's existing store shape (e.g. in
// `startos/init/index.ts` or wherever you declare `sdk.setupStore`).
//
// Example merge:
//
// export const initStore: StoreShape = {
// // ...your existing fields...
// ...licensingStoreDefaults,
// }
//
// The actions here write into these fields; your main process reads them.
export interface LicensingStoreFields {
/** The raw license key string, e.g. "LIC1-...-..." */
license_key: string | null
/** ISO timestamp of when the key was activated. */
license_activated_at: string | null
/** Last known validation status from the licensing server, for display. */
license_last_status: string | null
/** Invoice id of an in-progress purchase, if any. */
license_pending_invoice_id: string | null
}
export const licensingStoreDefaults: LicensingStoreFields = {
license_key: null,
license_activated_at: null,
license_last_status: null,
license_pending_invoice_id: null,
}