Railbase
GPTClaude

Architecture

The core, plugins, the vault data store, and the marketplace.

Updated

Video guide —watch on YouTube ↗

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 (SPA)              │
 │   │     /api/*        core REST + auto-CRUD + verbs    │
 │   │     /api/_admin/* admin-console API                │
 │   │     /<slug>       a plugin's site frontend          │
 │   │     /_pm          marketplace / plugin manager      │
 │   ├── 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 (a single-page app embedded in the binary).
  • /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 (a declarative widget descriptor) automatically at install.
  • /_pm — the marketplace / plugin-manager console (on by default; RAILBASE_PLUGIN_MANAGER=0 disables).

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.realtime to publish and $app.onEvent to 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 a declarative widget descriptor (bundle/widgets.json) 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.

The Go Register(app) form — compiling a plugin module into a build you control — is a dev/embedder build detail only, never the unit a customer receives and never 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. Code reaches it through the collection handle (app.Pool().Update/View) plus the storage helpers (Insert/GetByID/Save/Find/…); reads are a full collection scan plus a Go predicate, so tenant scoping is explicit. Each plugin gets its own namespaced collections; multi-tenant deployments can run a file per tenant. See Data & multi-tenancy. Horizontal scale over a shared store (a vault server) is PLANNED.

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.

Was this page helpful?Thanks for your feedback!