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
This commit is contained in:
Grant
2026-06-13 06:40:11 -05:00
parent 0508690d5a
commit ca32309ad9
5 changed files with 119 additions and 51 deletions
+87
View File
@@ -0,0 +1,87 @@
# Keysat Licensing — Instructions
Keysat is a Bitcoin-native, self-hosted licensing service for software
creators. You run your own instance, hold your own signing key, and issue
Ed25519-signed license keys that your software verifies offline. There is no
central authority and no shared database.
## Before you start
- **BTCPay Server is required.** Install and start BTCPay Server first — Keysat
uses it to take Bitcoin/Lightning payments and confirm settlement. StartOS
lists this dependency before it lets you install Keysat.
- **A clearnet domain is recommended if you sell to the public**, so buyers
anywhere can reach your checkout. LAN/Tor-only works for testing.
- **Zaprite is optional** (adds card payments). You connect it later from inside
the admin web UI; nothing to do up front.
## First-time setup
1. **Get your admin API key.** Open the **Actions** tab and run
**Show admin API key**. Copy it — you sign into the admin web UI with it the
first time.
2. **Open the admin dashboard.** Click **Launch UI** on the **Admin Web UI**
interface and paste the admin API key to sign in.
3. **(Recommended) Set a real password.** Run the **Set web UI password** action
(Actions tab, minimum 12 characters). After this the login page shows a
password field; the admin API key keeps working for automation.
4. **Connect your payment provider.** In the admin web UI's Settings, use the
one-click **Connect BTCPay** flow to authorize Keysat against your BTCPay
Server. (Optionally connect Zaprite here too.)
5. **Set your operator name** in the admin web UI — it appears on buyer-facing
checkout and receipts.
6. **Create what you sell.** Use **Create product** for each item, and
optionally **Create policy** to set per-product defaults (duration, grace
period, entitlements, seat cap, trial flag). A policy slugged `default` is the
one the public purchase flow uses.
Activation is optional. Keysat runs out of the box at the free **Creator** tier
(up to 5 products, 5 policies per product, and 10 active discount codes).
Activating a license lifts those caps and unlocks recurring billing and Zaprite
(card) payments. To activate, get a key at
[registry.keysat.xyz](https://registry.keysat.xyz), run the **Activate Keysat
license** action, and confirm with **Show Keysat license status**.
## Selling licenses
Share your **Licensing API** URL with buyers and bake it into your software as
the validation endpoint. Buyers call `POST /v1/purchase`, pay via BTCPay, and
Keysat issues a signed license key. Your software validates keys against
`POST /v1/validate` — including revocation checks, which return
`ok: false` with `reason: "revoked"`.
The same admin web UI covers manual license issuance (comps, press, trials),
suspension/unsuspension, revocation, machine management, discount codes,
outbound webhooks, and the audit log.
## Interfaces and exposure
- **Licensing API** (`/`) — public-facing. This is the URL you share with
customers and bake into your builds.
- **Admin Web UI** (`/admin`) — your dashboard. Restrict this interface to LAN or
Tor only; the public internet does not need to reach it.
- **BTCPay webhook endpoint** (`/btcpay`) — registered with BTCPay automatically
during the Connect BTCPay flow. Not for human use.
## Backups and uninstalling
Your data volume holds the SQLite database — which contains your server signing
key and every license record — and StartOS backs it up automatically. Your
self-license at `/data/keysat-license.txt` is included in the backup and
survives upgrades and reinstalls.
**Uninstalling deletes your signing key and all license records.** Once it is
gone, previously issued license keys no longer validate against this server. Back
up first if you plan to reinstall.
## Recovery
- **Locked out of the admin UI?** Run **Set web UI password** to set a new one,
or **Show admin API key** to sign in with the key.
- **Lost your Keysat license?** Re-run **Activate Keysat license** with your key.
## More
Full developer and integration documentation lives in the upstream repository
(`README.md` and `KEYSAT_INTEGRATION.md`) and at
[keysat.xyz](https://keysat.xyz).
+14 -19
View File
@@ -6,12 +6,10 @@
// writes it to /data/keysat-license.txt, and swaps its runtime tier
// to Licensed without a restart.
//
// In permissive builds (the default for local `make x86`) the daemon
// will start regardless and this action just records the tier. In
// enforce builds (compiled with KEYSAT_LICENSE_ENFORCE=1, used for
// the marketplace .s9pk) the daemon refuses to start without a valid
// license, and this action is the bootstrap path: install Keysat,
// run this action with your activation key, then start the service.
// 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'
@@ -36,9 +34,9 @@ export const activateLicense = sdk.Action.withInput(
async () => ({
name: 'Activate Keysat license',
description:
'Activate this Keysat install. Required for marketplace builds; ' +
'optional but recommended for source-built dev installs (signals support, ' +
'and lets the admin UI show your tier).',
'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',
@@ -80,7 +78,6 @@ export const activateLicense = sdk.Action.withInput(
product_id?: string
expires_at?: number
entitlements?: string[]
mode: string
}
message: string
}
@@ -132,7 +129,6 @@ export const showLicenseStatus = sdk.Action.withoutInput(
expires_at?: number
entitlements?: string[]
reason?: string
mode: string
}
if (j.tier === 'licensed') {
@@ -146,20 +142,19 @@ export const showLicenseStatus = sdk.Action.withoutInput(
message:
`License id: ${j.license_id}\n` +
`Expires: ${exp}\n` +
`Entitlements: ${ents}\n` +
`Build mode: ${j.mode}`,
`Entitlements: ${ents}`,
result: null,
}
} else {
return {
version: '1',
title: 'Unlicensed',
title: 'Creator (free tier)',
message:
`Reason: ${j.reason || 'no license configured'}\n` +
`Build mode: ${j.mode}\n\n` +
(j.mode === 'enforce'
? 'This is a marketplace build that requires a valid license to run. Use the "Activate Keysat license" action to bootstrap.'
: 'This is a permissive (dev) build. The daemon will keep running. Activate a license to see your tier reflected here.'),
`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,
}
}
+9 -6
View File
@@ -1,8 +1,9 @@
// Action: reveal the auto-generated admin API key.
//
// The operator rarely needs this — every other action in StartOS already
// carries the key for them — but it's useful if they want to script against
// the admin HTTP API directly.
// The operator needs this on first install to sign into the admin web UI
// (until they set a web UI password); afterward it's mainly for scripting
// the admin HTTP API directly, since every other StartOS action already
// carries the key for them.
//
// The BTCPay webhook secret used to live in the StartOS store; it now lives
// inside the daemon's own SQLite database, generated automatically during
@@ -35,9 +36,11 @@ export const showCredentials = sdk.Action.withoutInput(
version: '1',
title: 'Admin API key',
message:
`Used as 'Authorization: Bearer <key>' against /v1/admin/*. All ` +
`StartOS actions already supply this for you — only export it if ` +
`you intend to script against the admin API from outside the box.`,
`This is your admin API key — the 'Authorization: Bearer <key>' ` +
`credential for /v1/admin/*. Use it to sign into the admin web UI on ` +
`first install (until you set a web UI password). Every StartOS action ` +
`already supplies it for you, so you only need to export it to script ` +
`the admin API yourself.`,
result: {
type: 'single',
value: storeData.admin_api_key,
+4 -2
View File
@@ -15,13 +15,15 @@ export const manifest = setupManifest({
id: 'keysat',
title: 'Keysat Licensing',
license: 'LicenseRef-Keysat-1.0',
packageRepo: 'https://github.com/keysat-xyz/keysat-startos',
// packageRepo (the s9pk wrapper source) and upstreamRepo (the daemon source)
// are the same URL: the StartOS wrapper and the Rust daemon share one monorepo.
packageRepo: 'https://github.com/keysat-xyz/keysat',
upstreamRepo: 'https://github.com/keysat-xyz/keysat',
marketingUrl: 'https://keysat.xyz',
donationUrl: null,
docsUrls: [
'https://github.com/keysat-xyz/keysat/blob/main/README.md',
'https://github.com/keysat-xyz/keysat/blob/main/docs/INTEGRATION.md',
'https://github.com/keysat-xyz/keysat/blob/main/KEYSAT_INTEGRATION.md',
],
description: { short, long },
// A single data volume holds the SQLite database (which in turn holds the
+5 -24
View File
@@ -1,27 +1,8 @@
// Draft of the v0.2.0 milestone version entry.
//
// NOT YET WIRED INTO `versions/index.ts` — this file sits ready to
// use when we cut v0.2.0:0 from the alpha-iteration line. To
// activate:
// 1. In `versions/index.ts`:
// import { v0_2_0 } from './v0.2.0'
// export const versions = VersionGraph.of({
// current: v0_2_0,
// other: [v0_1_0], // ← so installs on 0.1.0:N can upgrade
// })
// 2. Build the .s9pk (`make x86`).
// 3. Publish via `~/.keysat/publish.sh` (the version-changed gate
// will fire because `0.2.0:0` differs from the recorded
// `0.1.0:N`).
//
// Why this draft exists separately:
// - The cut is an irreversible release decision for already-installed
// operators (downgrade paths exist in StartOS but they're sticky).
// - Wiring it in changes how StartOS computes the upgrade dialog
// shown to operators on registry refresh — best to QA the
// release-notes content in this file before flipping the switch.
// - Lets us write the v0.2.0 release notes carefully and then ship
// them all at once, rather than amending mid-build.
// The v0.2.0 milestone version entry — the current, active version on
// the v0.2 line. Wired into `versions/index.ts` as `current: v0_2_0`,
// with `v0_1_0` in `other` so installs on 0.1.0:N can upgrade. Routine
// wrapper updates bump the downstream revision here (`0.2.0:N`) before
// each build/publish; see startos-packaging.md.
//
// Version-string format reminder: ExVer is `<upstream>:<downstream>`.
// The `<upstream>` bump from 0.1.0 → 0.2.0 marks the milestone; the