v0.1.0:24 — Keysat licensing service end-to-end

Daemon, StartOS wrapper, admin SPA, public buy/thank-you pages,
discount codes, free-license redemption, Apply-discount UX,
self-licensing, and v0.1.0 release notes.
This commit is contained in:
Grant
2026-05-07 10:33:39 -05:00
parent 432250bffc
commit 6ac118ae70
90 changed files with 14896 additions and 524 deletions
@@ -0,0 +1,89 @@
-- Initial schema for the licensing service.
--
-- SQLite is used in WAL mode; all tables are intentionally flat and indexed
-- for the common query paths (validate by key_id, list by product, look up by
-- invoice_id from BTCPay webhooks).
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS products (
id TEXT PRIMARY KEY, -- UUID v4
slug TEXT NOT NULL UNIQUE, -- human-friendly id used in URLs
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
price_sats INTEGER NOT NULL, -- price in satoshis
active INTEGER NOT NULL DEFAULT 1, -- boolean; 0 hides from listings
metadata_json TEXT NOT NULL DEFAULT '{}', -- arbitrary developer metadata
created_at TEXT NOT NULL, -- ISO-8601 UTC
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_products_active ON products(active);
-- Invoices track BTCPay payment attempts. One invoice maps to at most one
-- license. If payment never completes, the invoice just sits in 'pending' /
-- 'expired' and no license is ever issued.
CREATE TABLE IF NOT EXISTS invoices (
id TEXT PRIMARY KEY, -- UUID v4 (our id)
btcpay_invoice_id TEXT NOT NULL UNIQUE, -- id from BTCPay Server
product_id TEXT NOT NULL,
status TEXT NOT NULL, -- pending | settled | expired | invalid
buyer_email TEXT, -- optional, supplied at purchase
buyer_note TEXT, -- optional purchase note
amount_sats INTEGER NOT NULL,
checkout_url TEXT NOT NULL, -- BTCPay checkout URL
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (product_id) REFERENCES products(id)
);
CREATE INDEX IF NOT EXISTS idx_invoices_btcpay_id ON invoices(btcpay_invoice_id);
CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status);
-- Licenses are the issued proofs-of-purchase. The `key_id` is what a client
-- presents when validating; the actual user-facing license key string is a
-- signed envelope containing this id plus metadata (see crypto module).
CREATE TABLE IF NOT EXISTS licenses (
id TEXT PRIMARY KEY, -- UUID v4, also the `license_id` in the signed payload
product_id TEXT NOT NULL,
invoice_id TEXT UNIQUE, -- NULL for manually-issued / comped licenses
status TEXT NOT NULL, -- active | revoked
fingerprint TEXT, -- optional machine fingerprint locked on first validation
bound_identity TEXT, -- optional user identity (email, pubkey, etc.) locked on first use
issued_at TEXT NOT NULL,
revoked_at TEXT,
revocation_reason TEXT,
metadata_json TEXT NOT NULL DEFAULT '{}',
FOREIGN KEY (product_id) REFERENCES products(id),
FOREIGN KEY (invoice_id) REFERENCES invoices(id)
);
CREATE INDEX IF NOT EXISTS idx_licenses_product ON licenses(product_id);
CREATE INDEX IF NOT EXISTS idx_licenses_status ON licenses(status);
-- Audit log of validation attempts. Useful for abuse detection and for
-- developers building rate-limiting policies on top.
CREATE TABLE IF NOT EXISTS validation_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_id TEXT,
product_id TEXT,
fingerprint TEXT,
result TEXT NOT NULL, -- ok | bad_signature | revoked | product_mismatch | fingerprint_mismatch | not_found
client_ip TEXT,
user_agent TEXT,
occurred_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_validation_license ON validation_log(license_id);
CREATE INDEX IF NOT EXISTS idx_validation_time ON validation_log(occurred_at);
-- Server-wide signing key. Stored here (rather than on disk) so a SQLite
-- backup captures the full server state. The private key is PEM-encoded.
-- Generated on first boot if no row exists.
CREATE TABLE IF NOT EXISTS server_keys (
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton
algorithm TEXT NOT NULL, -- 'ed25519'
public_key_pem TEXT NOT NULL,
private_key_pem TEXT NOT NULL,
created_at TEXT NOT NULL
);