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
This commit is contained in:
@@ -26,5 +26,85 @@ export const configFile = FileHelper.json(
|
||||
recap_admin_password_hash: z.string().default(''),
|
||||
recap_admin_password_salt: z.string().default(''),
|
||||
recap_admin_session_secret: z.string().default(''),
|
||||
// PodcastIndex credentials — used to resolve Spotify share URLs to
|
||||
// their underlying RSS audio enclosure. Free tier signup at
|
||||
// api.podcastindex.org. Apple Podcasts URLs resolve without auth.
|
||||
podcastindex_api_key: z.string().default(''),
|
||||
podcastindex_api_secret: z.string().default(''),
|
||||
// ── Multi-tenant cloud-mode fields (added 0.2.77) ──
|
||||
// recap_mode: "single" → existing self-hosted, one operator, no auth
|
||||
// "multi" → cloud mode with email + magic-link auth,
|
||||
// per-user library, BTCPay subscriptions
|
||||
// The .s9pk defaults to single. Operators flip to multi via the
|
||||
// "Enable multi-tenant mode" StartOS action.
|
||||
recap_mode: z.enum(['single', 'multi']).default('single'),
|
||||
// Public URL used in magic-link sign-in emails. Must be the
|
||||
// ClearNet URL pointing at the Recap UI (typically a domain
|
||||
// routed through Start Tunnel). Without this set, magic-link
|
||||
// emails won't have a working sign-in link.
|
||||
recap_public_url: z.string().default(''),
|
||||
// Shared "operator key" for the core-decoupling cloud path. The secret
|
||||
// that authenticates THIS Recap server to the Recap Relay so it can
|
||||
// vouch for its signed-in users by account-id (X-Recap-User-Id) instead
|
||||
// of attaching a per-user Keysat license. Must EXACTLY match the relay's
|
||||
// own relay_cloud_operator_key. Empty = the cloud user-id path is
|
||||
// disabled and paid users fall back to the operator's relay pool.
|
||||
// Set via the "Set Relay Operator Key" StartOS action. Server-side
|
||||
// only — never sent to the browser. Picked up live by the config poll
|
||||
// (no restart needed), same as the provider API keys.
|
||||
recap_relay_operator_key: z.string().default(''),
|
||||
// Per-tenant default credit allowance — only used when running in
|
||||
// multi mode on a self-hosted operator's StartOS server. New
|
||||
// tenants (family members who sign up on the operator's domain)
|
||||
// start with this many credits. The operator's relay-credit pool
|
||||
// is debited as their tenants summarize. Doesn't apply to the
|
||||
// canonical cloud deployment because cloud users have their own
|
||||
// keysat licenses + relay-side credit pools.
|
||||
tenant_default_credits: z.number().int().nonnegative().default(5),
|
||||
// How often a tenant's "replenishable" credit bucket is refilled
|
||||
// to tenant_default_credits.
|
||||
// "off" — no refill; the signup-grant is one-time
|
||||
// "daily" — anniversary-aligned 24h
|
||||
// "weekly" — anniversary-aligned 7d
|
||||
// "monthly" — anniversary-aligned calendar month
|
||||
// Anniversary-aligned = anchored to each tenant's last_replenish_at
|
||||
// (set when they signed up or when the operator first turned the
|
||||
// period on). Purchased credits + admin grants are persisted in
|
||||
// tenant_credits.purchased_balance and NEVER affected by refills —
|
||||
// only the replenish_balance bucket gets reset to the configured N.
|
||||
tenant_credit_replenish_period: z
|
||||
.enum(["off", "daily", "weekly", "monthly"])
|
||||
.default("off"),
|
||||
// Anonymous-trial knobs (multi-mode only). Visitors who land on
|
||||
// recapapp.xyz without an account get N free summaries gated by a
|
||||
// browser cookie before being prompted to sign up. Set
|
||||
// trial_credits_per_visitor=0 to disable trials (return to an
|
||||
// auth-wall landing). trials_per_ip_per_day caps how many distinct
|
||||
// trial cookies one IP can mint in 24h — anti-script-abuse floor.
|
||||
trial_credits_per_visitor: z.number().int().nonnegative().default(1),
|
||||
// Lifetime cap on the number of distinct trial cookies one IP can
|
||||
// mint. Was previously rolling-24h; switched to lifetime so a user
|
||||
// who clears cookies can't replay the trial every day. The legacy
|
||||
// field name `trials_per_ip_per_day` is preserved below as a
|
||||
// read-only alias so installs that already have it set don't lose
|
||||
// their value during the rename — anon-trial.js prefers
|
||||
// _lifetime if set, falls back to _per_day if not.
|
||||
trials_per_ip_lifetime: z.number().int().positive().default(5),
|
||||
// Legacy alias — read for backward compatibility with v0.2.77–0.2.83
|
||||
// installs that wrote the per-day variant. Will be removed once
|
||||
// all known installs have re-saved their config under the new key.
|
||||
trials_per_ip_per_day: z.number().int().positive().default(5),
|
||||
// ── SMTP — synced from StartOS System SMTP via the SDK ──
|
||||
// main.ts subscribes to effects.getSystemSmtp() and writes these
|
||||
// fields here whenever the operator changes the system SMTP. The
|
||||
// server reads them through the same file-poll as everything else
|
||||
// and (re)builds its nodemailer transport. NEVER edit these
|
||||
// fields directly — they get overwritten on the next sync.
|
||||
smtp_host: z.string().default(''),
|
||||
smtp_port: z.number().int().default(0),
|
||||
smtp_security: z.enum(['starttls', 'tls']).default('tls'),
|
||||
smtp_username: z.string().default(''),
|
||||
smtp_password: z.string().default(''),
|
||||
smtp_from: z.string().default(''),
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user