Docs
/
build knowledge

Imperium hosting

- Configs are declarative mode+data JSON (not JSX), so there is NO server-side compilation / vm / AST validation. The safe schema is the config. Resolving a host = a DB read + a gate check.

Build-knowledge — Imperium hosting (Studio dashboard + production serving)

How a Leumas tenant hosts their own Imperium sites — set up, edit (any mode, live preview), publish to production, connect external apps — and how a white-label buyer runs the same thing in their own ecosystem. This is the build-knowledge entry for the hosting spine.

The mental model

config doc (api.db('configs'))  =  one subdomain
  { id(=subdomain), appName, mode, data, seo, hosting:{ ownerUserId, tenantId, status, subdomain, domains } }
        │
Studio ▸ Hosting ──-> /api/hosting/* ← @leumas/hosting (owner+tenant CRUD + publish lifecycle)
  useCapability('imperium.hosting')                 gated by ↓
                                              @leumas/entitlements  (policy → capabilities + quotas, DATA)
visitor {sub}.leumas.tech ──-> /api/hosting/resolve?host= ← same @leumas/hosting core (published+entitled gate)
                                              → DynamicPage renders mode+data  (+ per-config SEO/sitemap/robots)
standalone white-label:  @leumas/imperium-server  imports the SAME @leumas/hosting core (one core, two deployments)
  • Configs are declarative mode+data JSON (not JSX), so there is NO server-side compilation /

vm / AST validation. The safe schema is the config. Resolving a host = a DB read + a gate check.

The three new units

  • @leumas/entitlements (shared/packages/entitlements) — the swappable membership-ACCESS axis

(sibling to PassNode's monetization axis). Plans declare capabilities (booleans) + quota limits (numbers) as DATA in the Studio-editable entitlement_policies collection (one doc per plan/service), with per-user overrides in membership.metadata. Resolver merges active memberships (OR booleans, MAX limits). Backend: entitlements.requireCapability(cap) / checkQuota(user, cap, count) / forUser. Frontend: @leumas/features useCapability(cap) / useLimit(cap) / <RequireCapability cap fallback> off GET /entitlements/me. Wired in app.js beside PassNode (load + refresh on the /db change-feed). Change a rule = edit a doc; zero code change.

  • @leumas/hosting (shared/engines/plugins/hosting) — the hosting core: router.js (/api/hosting

owner+tenant CRUD, publish/unpublish/suspend, subdomain claim, domains, proxy-check, telemetry), resolve.js (host→published+owner-entitled config or suspension fallback; strips secrets), seo.js (meta + sitemap.xml + sitemap-index + robots.txt), serve.js (public resolve + SEO routers), proxy.js (SSRF-safe reverse proxy — denies RFC1918/loopback/link-local/ULA/metadata/CGNAT, DNS-rebind pin, strips visitor Cookie/Authorization, timeout/size caps, gated by imperium.reverseProxy; plus createProxyUpgradeHandler so WebSockets — HMR, socket.io — survive the proxy under the same rules), targets.js (what a host serves: config · proxy · server · static · bundle · redirect), localApps.js (the server kind's supervisor — spawns a member's app from a folder+port, restarts it with backoff, keeps a log ring; self-host only, inert unless the deployment passes allowSpawn), subdomains.js (normalize/reserved/host helpers).

mode vs target — the name collision worth knowing. A mode decides what a page renders; a target decides whether a page is rendered at all. mode:'server' frames someone's app in an iframe. target.kind:'server' is the app, served as the domain itself. Both exist; they are not alternatives to each other. The dispatcher is mounted by leumas-api and imperium-server, so a target behaves identically on an appliance and on a white-label box. Guard: pnpm smoke:hosting.

  • @leumas/imperium-server (shared/services/imperium-server) — the standalone white-label edition;

createImperiumApp() reuses the SAME @leumas/hosting resolve/SEO, injects per-subdomain SEO into the web bundle's index.html, exposes /healthz+/readyz, and sits behind the wildcard deploy/Caddyfile (*.leumas.tech → identical worker pool, preserve Host). v1.0: bin/create-imperium.mjs scaffolds an instance (runtime layout + generated secrets + seeded __global and first site, idempotent), README.md is the host-your-own-infra guide, entitlements come from a Leumas-signed offline token on a licensed appliance or local policies + committed defaults when self-hosted. Package it with node ops/tools/exe-builder/build-imperium.mjs (pnpm deploy hoisted — symlinked deps would break the moment the folder is copied) → a relocatable ~117 MB bundle with start.cmd/start.sh.

Nested multi-tenancy

config.hosting.ownerUserId + config.hosting.tenantId (req.user.tenantId from users.metadata, default 'leumas'). Scope in the hosting router: end-user sees own · tenant-admin (role tenant_admin or capability imperium.tenantAdmin) sees the whole tenant · Leumas admin sees all. A white-label buyer is a tenant whose members host under imperium.* capabilities their plan grants.

Studio surfaces (products/leumas-studio/src/admin/hosting/)

WebsitesHome ("My Websites": cards with live DynamicPage thumbnails, status, quota, draft-preview links) · CreateSiteWizard (/admin/site-wizard: template-or-blank → live subdomain claim → schema form beside the live preview → publish) · SiteEditor (tabs: Content · SEO · Domains · Connect · Shield · Analytics · Danger) · GlobalSettings (the __global layer) · ViewModes (the view-type index + custom-type upload) · OperatorTab (admin cross-lane inventory) · PreviewFrame · HostingPaywall. The whole domain is gated by ONE <RequireCapability cap="imperium.hosting">. ConfigManager survives as the operator raw-JSON lane — it writes through the hosting router, never api.db('configs').

Modes — the data contract

Every mode's data shape is DECLARED once in config-registry/src/dataSchemas.js (descriptor fields). That single source drives: the generated Studio form (@leumas/ui SchemaForm), server-side validation on every save/publish (validateData → 400 with a field-level error list), the new-site template, and the docs. Every built-in mode has a renderer, and a guard asserts both directions of that — no mode without a renderer, no schema without a mode. The count is deliberately not written here. It was "29" in this file while the real number was 34, and the same stale figure is repeated in four other places; a number in prose is a future lie. The live list is at docs.leumas.tech/modes, generated from MODES at build time.

User-uploaded view types: a doc in the view_modes collection carries its own field schema + default template + sandboxed HTML renderer (x- namespace, CustomModePage). It flows through the SAME validation, SchemaForm and DynamicPage path as a built-in — zero new engine code. Anonymous visitors fetch them from the PUBLIC GET /api/hosting/view-modes (/db is auth-gated and would 401 a first-time visitor).

Serving guarantees

A config serves only when published (hosted lane) or explicitly firstParty: true (operator lane) — an unclaimed raw doc 404s. Served output is the merge __global__global--<tenantId> → site (local wins, arrays concat), behind a TTL read-cache that every write busts. Drafts are visible only through a signed 30-minute preview-token, noindexed. Storage-backed modes (folder/gallery/exe) read a per-site root under IMPERIUM_STORAGE_ROOT, traversal-guarded; raw filesystem roots are self-host-only and env-allowlisted.

Extend it

  • New capability/quota → add a key to an entitlement_policies doc (data). Read it via useCapability/

useLimit (frontend) or requireCapability/getLimit (backend).

  • New built-in mode → add the page shell, register it in DynamicPage (or a package registerModes.js), and

add its dataSchemas.js entry (fields + template). check-imperium FAILS on a mode without either.

  • New view type without touching code → upload it in Studio ▸ View Modes.
  • Agents drive it through the imperium adapter (auto MCP + functioncalls) and the Leviathan page

capabilities on the Websites surfaces.

  • Guards: pnpm check:imperium (schemas, renderers, templates, write-lane, component-registry drift) ·

pnpm smoke:imperium (full e2e through the standalone server).

  • Migration ledgers: ops/migration/imperium-hosting.json, imperium-modes.json, imperium-configs.json.
Source shared/services/knowledge/build-knowledge/imperium-hosting.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt