Files
simple-pos-system/PLANS AND STRATEGIES/04_CLOUD_BACKEND.md

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_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