Architecture
The core, plugins, the vault data store, and the marketplace.
Updated
Railbase is one binary that supervises a small set of moving parts. Understanding them makes the install, update, and licensing flows obvious.
The big picture
your server
┌────────────────────────────────────────────────────┐
│ Railbase core (the binary) │
│ ├── HTTP server :8095 │
│ │ / public site SPA (optional) │
│ │ /_/ admin console + marketplace │
│ │ /api/* core REST + auto-CRUD + verbs │
│ │ /api/_admin/* admin-console API │
│ │ /<slug> a plugin's site frontend │
│ │ /_pm/* plugin-manager endpoints │
│ ├── vault single-file encrypted data store │
│ ├── goja runtime (runs plugin JS in-process) │
│ │ ├── plugin A (decrypted JS+schema bundle) │
│ │ └── plugin B (decrypted JS+schema bundle) │
│ └── plugin manager (install/license/lifecycle) │
└────────────────────────────────────────────────────┘
│ syncs with (catalog, licenses,
▼ bundles, payments)
railbase.app — vendor licensing & distribution
A plugin's verbs always live at /api/<slug>/…. A plugin is data the core
runs, not a separate program: its encrypted JS+schema bundle lives in the
core's _plugins Vault row and, once the license gate decrypts it, runs inside
the core's own goja runtime — so its verbs are served by the core directly,
in-process.
The core
The core is the always-on process. It owns the HTTP server, the data store, auth, the job runner, and the admin console. It boots in well under a second and runs happily on a small VPS.
Key routes:
/— the public site SPA (what end users see), optional./_/— the admin console, including Marketplace at/_/marketplace./api/*— core REST, auto-CRUD, and plugin verbs./api/_admin/*— the admin-console API./<slug>— a plugin's end-user frontend, mounted on the site SPA from the plugin's bundle manifest automatically at install./_pm/*— plugin-manager and marketplace proxy endpoints (on by default;RAILBASE_PLUGIN_MANAGER=0disables). Destructive actions require an admin session; the public admin UI is/_/marketplace.
Plugin verbs answer at /api/<slug>/…, served by the core directly: the
plugin's bundle registers them on the core router (via $app.routerAdd) when its
decrypted JS loads into the goja runtime. There is no per-plugin port and no
proxy hop.
Plugins: data the core runs
A plugin ships as a data-resident bundle — encrypted JS plus a collection
schema — not a standalone program. On purchase the marketplace installs that
bundle into the core's _plugins Vault row (the JS is AES-256-GCM-encrypted at
rest, so a copy of the .vault file never reveals plugin source). The license
gate is the execution point: the core decrypts and loads a plugin's code only
while its license permits it (active or trial); a dormant — unpaid, expired, or
revoked — plugin is never decrypted and has zero behavior.
Once loaded, a plugin runs in-process in the core's goja runtime and reaches everything through mediated host capabilities — never the core's internals:
- verbs via
$app.routerAdd, served at/api/<slug>/…, - data via the tenant-scoped
$app.dao(the platform's shared store — no per-plugin database), - events via
$app.realtimeto publish and$app.onEventto consume, - background work via
$jobs, - identity via
e.auth(trusted server-side, never a client header), - plan and quota via
$app.license.
A plugin's end-user UI ships inside the same bundle as bundle/manifest.json
(manifest.ui.pages) and the core's site shell mounts it at /<slug> on install
— one marketplace action delivers verbs, schema, and UI with no rebuild. Because
plugin code is data the core executes, install, update, and remove happen at
runtime by writing or flipping the Vault row — no subprocess to launch, no port
to open, no redeploy. The full lifecycle is in
How plugins work.
Internal dev/embedder workflows can compile a controlled host build for engineering use, but that is not a customer artifact and not a distribution channel. The single shipped, sold form is the data-resident bundle above.
The vault data store
Persistence is a single encrypted file (railbase.vault) managed by an
in-process, MVCC document engine that stores CBOR documents — no SQL, no
PostgreSQL, no connection strings, no sidecar. Tenant scoping is explicit:
tenant-aware collections carry tenant_id, and production hot paths use
declared secondary indexes rather than SQL queries. Each plugin gets its own
namespaced collections inside the platform's shared Vault. See
Data & multi-tenancy.
railbase.app: the vendor plane
Your instance is a client of railbase.app, the vendor's server. It reaches out to:
- discover plugins and prices (catalog) and the vendor's signing key (pubkey),
- complete a purchase (payment runs on railbase.app; your server is never a card processor),
- download the signed, encrypted bundle for a plugin you're licensed for,
- keep licenses fresh via a periodic license check (renewals, revocations).
The trust anchor is a pinned public key: the core only installs a bundle whose signature verifies against the key it has pinned for the vendor. This is why acquisition is marketplace-only — see How plugins work and Licensing & seats.