3.1 KiB
3.1 KiB
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: <site_id>
Header: X-Site-Key: <secret_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_keyper 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