bonaminandClaude Opus 5.5 96a014ecbe feat(cloud): heartbeat returns a signed license token (Ed25519)
Sites now enforce their license offline (client-services KI-006): the
cloud is needed to renew a license, not to run it. Each heartbeat carries
license_token = base64url(payload).base64url(signature), payload
{v:1, site_id, active, locked, lock_reason, expires_at, issued_at}, signed
with LICENSE_SIGNING_KEY (base64 raw Ed25519 private key, cloud .env). The
matching public key is built into the site code, so a stored token can't be
edited and a clock can't be set before issued_at unnoticed.

Additive field only (old sites ignore it). Without the key the field is
null and a warning is logged. Pins cryptography==46.0.4 (was transitive).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:16:32 +03:00

cloud-service — VPS Pack

Everything that runs on the developer's VPS and serves all client sites. This folder is its own git repo, nested inside the xenia-pos parent repo.

Read the global rules first: ../../docs/README.md. This file adds only the rules specific to this pack. The feature catalog, the local ↔ cloud contract, known issues and site records all live in the parent's docs/.


Services

Service Stack Host port Audience
cloud_backend FastAPI + SQLAlchemy + SQLite (${BACKEND_DATA}) 8001 local sites (heartbeat/sync), sysadmin, connect apps
sysadmin_panel React + Vite (VITE_CLOUD_URL baked in at build) 5175 the developer only
connect_frontend nginx serving two SPAs: menu-app at /menu/, manager-app at /manage/ 3100 restaurant customers (QR menu), remote managers

TLS and domains are handled in front of these ports on the VPS (xenia-api.bonamin.net → cloud_backend, xenia-admin.bonamin.net → sysadmin panel, registry.bonamin.net → image registry).

Routers (cloud_backend/routers/)

Prefix File Auth Used by
/api/auth auth.py admin login sysadmin panel
/api/sites sites.py admin bearer sysadmin panel
/api/heartbeat heartbeat.py X-Site-ID + X-Site-Key local sites
/api/menu menu.py public GET; site headers for sync menu-app, local sites
/api/orders orders.py public create/status; site headers for pending/synced/status menu-app, local sites
/api/manager manager_auth.py manager bearer manager-app, sysadmin
/api/remote remote_dashboard.py manager bearer; site headers for POST /snapshot manager-app, local sites

Pack-specific rules

CL-1. Old sites are always talking to you. Sites upgrade slowly, and some may be several versions behind. Every endpoint a site calls (the contract, C-1 … C-7) must stay backward compatible:

  • add optional fields only
  • never rename, remove or retype fields
  • never make a new request field required

Any change to one of these endpoints updates the parent's docs/reference/cloud-contract.md (global Rule 6).

CL-2. Schema changes go through _run_migrations() in cloud_backend/main.py: additive ALTER TABLE ... ADD COLUMN only, and never drop columns. Superseded columns stay, with a comment (e.g. menu_hours_en).

CL-3. The cloud is never on the service-critical path. If the cloud is down, restaurants must keep taking orders (global Rule 8). Don't design features where the local site blocks while waiting on the cloud.

CL-4. Public endpoints are hostile territory. menu-app endpoints are reachable by anyone on the internet. Validate everything, rate-limit where it makes sense, and never return data from another site. Site secrets (SITE_KEY) are shown once, at registration.

CL-5. Frontend URLs are baked in at build time. VITE_CLOUD_URL is a build arg, so changing it means rebuilding the image, not restarting it.

Common commands

# Deploy on the VPS
cd ~/stacks/xenia-pos-cloud && docker compose up -d --build

# Dev
cd cloud_backend && uvicorn main:app --reload --port 8001
cd sysadmin_panel && npm run dev
cd connect_frontend/menu-app && npm run dev
cd connect_frontend/manager-app && npm run dev

Releasing a new client version also means bumping LATEST_VERSION in cloud_backend/.env and redeploying. See the parent's DEPLOYMENT_GUIDE.md.

S
Description
This this is the Cloud Backend + Sysadmin panel, for the Xenia POS system
Readme
375 KiB
Languages
JavaScript 71.9%
Python 26%
Dockerfile 0.9%
HTML 0.8%
CSS 0.4%