Files
recap/startos/actions/enableMultiTenantMode.ts
Keysat 0ae59f3550 Add multi-tenant cloud mode: self-serve purchase, credit metering, core-decoupling
Introduces RECAP_MODE=multi alongside single-mode self-host:
- Tenant auth + accounts (magic-link via System SMTP), per-tenant credit pool,
  anonymous trial minting with per-IP/-64 caps
- Self-serve Pro/Max purchase: inline Lightning (BTCPay) + card (Zaprite),
  prepaid 30-day periods, expiry-reminder emails
- Core-decoupling: relay owns cloud tier/expiry keyed by Recaps user-id
- SQLite (better-sqlite3) schema for multi-mode; filesystem unchanged for single
- StartOS actions/versions through 0.2.155
2026-06-13 14:25:05 -05:00

65 lines
2.6 KiB
TypeScript

import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Multi-tenant mode (a.k.a. cloud mode) turns the self-hosted Recaps
// into a multi-user app served over the web. Adds email + magic-link
// auth, per-user libraries, per-user keysat licenses, and BTCPay-based
// subscriptions. The .s9pk defaults to single-mode (one operator, no
// auth) so installing Recaps doesn't surprise anyone — multi is the
// deliberate opt-in for operators who want to host the app for others.
//
// Prerequisites before enabling:
// 1. StartOS System SMTP must be configured + tested. Magic-link
// emails fail otherwise.
// 2. "Set Recaps public URL" action must be run with the ClearNet URL
// where your Recaps will live (e.g. https://recap.example.com),
// so the sign-in emails contain a working link.
// 3. A keysat issuer the relay trusts must be reachable, since each
// cloud user gets a keysat-minted license at signup.
//
// Switching back to single mode is non-destructive — multi-tenant data
// (the user DB at /data/recap.db) stays on disk and is restored if you
// flip back to multi later. The operator-owner's library lives under
// /data/history/owner/ in either mode.
const inputSpec = InputSpec.of({
mode: Value.select({
name: 'Mode',
description:
'Single = original self-hosted experience (one operator, no auth). Multi = cloud mode (email/magic-link auth, multi-user, BTCPay subscriptions). Switching takes effect on next service restart.',
default: 'single',
values: {
single: 'Single (self-hosted, no accounts)',
multi: 'Multi (cloud, email auth + subscriptions)',
},
}),
})
export const enableMultiTenantMode = sdk.Action.withInput(
'enable-multi-tenant-mode',
async ({ effects }) => ({
name: 'Enable Multi-Tenant Mode',
description:
'Switch Recaps between single-user (self-hosted) and multi-tenant (cloud) modes. Multi mode adds email-based sign-in, per-user libraries, and BTCPay subscriptions. Configure SMTP and the public URL before enabling.',
warning:
'Switching to multi mode requires StartOS SMTP to be configured and the Recaps public URL set. The service restarts on save.',
allowedStatuses: 'any',
group: 'Multi-Tenant',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return { mode: (config?.recap_mode as 'single' | 'multi') || 'single' }
},
async ({ effects, input }) => {
await configFile.merge(effects, { recap_mode: input.mode })
return null
},
)