# Guide 04 — Cloud Backend (FastAPI on VPS) ## Overview A lightweight FastAPI backend hosted on your VPS. It does NOT handle restaurant operations — that's the local backend's job. Its sole responsibilities are: site registration, license management, remote lock/unlock, and receiving periodic heartbeats from local backends. --- ## Project Structure ``` cloud_backend/ ├── main.py ├── config.py # VPS settings, master secret key ├── database.py # PostgreSQL (or SQLite for simplicity) ├── models/ │ ├── site.py │ └── admin.py ├── schemas/ ├── routers/ │ ├── auth.py # Sysadmin login │ ├── sites.py # Site management │ └── heartbeat.py # Receives check-ins from local backends └── requirements.txt ``` --- ## Database Schema ### `admins` table ``` id INTEGER PK username TEXT UNIQUE password_hash TEXT role TEXT # 'sysadmin' (you, always) ``` ### `sites` table ``` id INTEGER PK site_id TEXT UNIQUE # UUID, generated on registration name TEXT # Restaurant name owner_name TEXT contact_email TEXT secret_key TEXT # Shared secret for heartbeat auth (hashed) is_active BOOLEAN DEFAULT TRUE is_locked BOOLEAN DEFAULT FALSE lock_reason TEXT NULL license_expires_at DATETIME created_at DATETIME last_seen_at DATETIME NULL # Updated on each heartbeat last_seen_ip TEXT NULL ``` --- ## API Endpoints ### Auth — `/api/auth` ``` POST /login Body: { username, password } Returns: JWT token (for sysadmin panel use) ``` ### Sites — `/api/sites` All routes require sysadmin JWT. ``` GET / # List all sites with status POST / # Register new site → returns site_id + secret_key GET /:site_id # Site detail + last heartbeat info PUT /:site_id # Update site info / extend license POST /:site_id/lock # Remotely lock a site Body: { reason: "..." } POST /:site_id/unlock # Remotely unlock a site DELETE /:site_id # Deregister site ``` ### Heartbeat — `/api/heartbeat` Called by local backends every 6 hours. No sysadmin auth — uses site's own secret key. ``` POST / Header: X-Site-ID: Header: X-Site-Key: Body: { version: "1.0.0", uptime_seconds: 12345 } Returns: { licensed: true, locked: false, lock_reason: null, expires_at: "2026-12-31T00:00:00Z" } ``` ### Health — `/health` ``` GET /health # Public, no auth. Cloud liveness check. ``` --- ## Deployment Notes - Host on your VPS with nginx reverse proxy + SSL (Let's Encrypt) - Use PostgreSQL for the cloud DB (more robust than SQLite for a server) - Run with: `uvicorn main:app --host 0.0.0.0 --port 8001` - Set up systemd service for auto-restart --- ## Security Notes - The `secret_key` per site is generated once at registration and never shown again (like an API key) - Local backends store it in their `config.py` / environment variable - Heartbeat endpoint rate-limited to prevent abuse - All sysadmin routes require JWT with short expiry