Skip to main content

System Overview

SAPOT is a LAN-first disaster-response communications platform. It is designed to operate entirely on a local-area network (LAN) managed by a MikroTik router, with no dependency on internet connectivity for its core functions.


Design Principles

  • LAN-first. Messaging, calls, peer discovery, and GPS sharing all function on the LAN without internet. See ADR 0005.
  • Local database. The mobile app maintains a full local copy of relevant data in WatermelonDB (SQLite). Sync with the server is incremental and resumable. See ADR 0003.
  • P2P media. Voice and video calls are WebRTC peer-to-peer. The server relays only the SDP and ICE signalling messages; it never carries call media. See ADR 0004.
  • E2E encryption. Messages are encrypted at rest and in transit using NaCl box (ECDH key agreement, per-conversation keys). The server cannot read message content. See ADR 0001.
  • SMS fallback. If a recipient is not reachable on the LAN, the GSM module sends an SMS via an Arduino-controlled modem.

Components and Responsibilities

Mobile App (mobile-app/sapot-mobile-app/)

The primary user-facing component. An Expo/React Native Android app.

Responsibilities:

  • Peer-to-peer messaging over WebRTC data channels (LAN) or LAN TCP+TLS
  • Voice and video calls (WebRTC P2P; server relays signalling only)
  • LAN peer discovery via Zeroconf/mDNS
  • Live GPS location sharing (rescuers only; streamed over a dedicated WebSocket to the server)
  • Server-fetched announcements (role-filtered, expiry-aware)
  • Local database (WatermelonDB/SQLite) with incremental pull/push sync
  • E2E encryption (NaCl box, per-conversation ECDH keys, at-rest encryption)
  • Guest-to-authenticated account migration
  • Foreground-service connectivity while the Android app process remains alive

Key dependencies: Expo Router, react-native-webrtc, react-native-tcp-socket (TLS), react-native-zeroconf, WatermelonDB, tweetnacl, expo-secure-store, @maplibre/maplibre-react-native.

See the mobile app architecture for the full service map, DI wiring, and encryption details.


Server (server/)

The FastAPI backend. Deployed as a Gunicorn process behind an Nginx reverse proxy.

Responsibilities:

  • Authentication and session management (PyJWT, pwdlib)
  • WebSocket signalling relay for WebRTC negotiation
  • Incremental sync endpoint (GET /sync/pull, POST /sync/push)
  • GPS location ingest and relay (WebSocket per user)
  • User management (registration, email verification, password reset, profile pictures)
  • Public key distribution for peer encryption key exchange
  • Admin operations (user suspension, announcements, role management)
  • MikroTik router telemetry collection (RouterOS API)
  • SMS dispatch proxy (delegates to the GSM module via HTTP)
  • Captive portal integration
  • Activity logging (JSON + text rotating log files; DB write for mutating requests)
  • Rate limiting via slowapi
  • Announcement lifecycle management (background expiry loop)
  • Router metrics collection (background polling loop)

Key dependencies: FastAPI 0.128, Uvicorn/Gunicorn, SQLModel 0.0.31 + SQLAlchemy 2.0, PyMySQL (MariaDB), redis.asyncio (pub-sub for WebSocket fan-out), RouterOS-api, pyserial, PyJWT, pwdlib, slowapi, fastapi-pagination, Sentry SDK.

Internal structure: api/ routers → db_operations/ service layer → models/.

API routers: admin, auth, captive_portal, download, forgot_password, gps, gsm, keys, mikrotik, peer_connection, ping, profile_picture, public_chat, sync, testing (dev only), update_info, user_keys, user_utils, verify_email, wrapped_key.

Note: The testing router is only mounted when ENVIRONMENT=development or staging (see app/main.py); it is excluded from production builds by default. See SECURITY.md for the fix history.


Admin Frontend (admin-frontend/sapot-admin/)

A Next.js App Router web application for admins and rescuers.

Responsibilities:

  • User dashboard (user list, roles, suspension)
  • Live GPS map (rescuer view, powered by offline tileserver)
  • Network analytics (MikroTik interface traffic, router health)
  • Announcement management

Architecture: Next.js server components and server actions act as a Backend-for-Frontend (BFF), proxying requests to the FastAPI server. Client-side encryption is applied where needed. Route handlers are used for streaming or long-poll endpoints.


GSM Module (GSM-module/)

A hardware SMS gateway.

Responsibilities:

  • Exposes an HTTP API consumed by the SAPOT server
  • Controls an Arduino (or direct serial modem) via pyserial using AT commands
  • Sends outbound SMS messages to phone numbers when LAN delivery is not possible

Stack: FastAPI, pyserial.


Captive Portal (captive-portal/)

Static login pages hosted by the MikroTik router.

Responsibilities:

  • Shown to users when they first join the SAPOT Wi-Fi network
  • Authenticates users against the MikroTik user database (or delegates to the SAPOT server)
  • Grants network access on success

Stack: Static HTML, CSS, JavaScript. Served by MikroTik's built-in hotspot feature.


Tileserver (tileserver/)

An offline map tile server.

Responsibilities:

  • Serves raster or vector map tiles for the GPS map in the admin frontend and mobile app
  • Operates without internet; tiles are pre-loaded onto the server

Note: Technology stack and tile format (e.g. MBTiles, PMTiles) need verification.


Communication Matrix

SourceDestinationProtocol / ChannelPurpose
Mobile appServerHTTPS RESTAuth, sync pull/push, user search, key exchange, profile pictures, announcements, admin actions
Mobile appServerWSS WebSocket (/ws/)WebRTC signalling (SDP/ICE relay), presence, active-users
Mobile appServerWSS WebSocket (/gps/ws/<userId>)Live GPS location streaming
Mobile appMobile appWebRTC P2P (data channel)Chat messages
Mobile appMobile appWebRTC P2P (media)Voice and video call audio/video streams
Mobile appMobile appLAN TCP + TLSSignalling over direct peer connection (when WS unavailable); planned fallback for chat
Mobile appMobile appmDNS (Zeroconf)LAN peer discovery, address updates
Admin frontendServerHTTPS (Next.js BFF)All dashboard operations
ServerMikroTik routerRouterOS APIInterface traffic, router health metrics
ServerGSM moduleHTTP APISMS dispatch requests
GSM moduleArduino / modemSerial (pyserial, AT commands)AT command execution for SMS
MikroTik routerCaptive portalHTTP (internal)Login page serving and hotspot auth

This diagram is a protocol-level view (who talks to whom, over what channel, and why). For the physical/deployment topology (hosts, ports, processes), see component-map.md.


Roles

RoleDescriptionKey Permissions
adminSystem administratorFull admin dashboard access, user management, announcements, network config
rescuerEmergency responderLive GPS map (all users), announcements, all communication features
userAuthenticated end userMessaging, calls, GPS sharing with rescuers, view announcements
guestUnauthenticated userLAN messaging and calls only; no GPS sharing; no server-dependent features

Role is stored in peers.role (WatermelonDB, added in schema v9; current schema is v11) and server-side. Role is displayed as a badge in chat lists and message bubbles. See ADR 0006 for why this flat four-role model was chosen over a fine-grained permission system.

Resolution is split across two mechanisms — _resolve_role does not cover all four roles:

  • admin / rescuer / user — resolved by _resolve_role (server/app/db_operations/user_search.py) from the authenticated User's satellite role rows: admin row → admin, else rescuer row → rescuer, else user. It never returns guest.
  • guest — not a JWT-bearing role. Guests are placeholder user rows with a matching guest row, created by POST /sync/push for unknown peer IDs and by the GSM phone-onboarding flow. Guest-ness is queried per peer via GET /keys/{peer_id}/type ({"is_guest": bool}), which reports whether the peer has a server-registered PeerKey.

System Boundaries

  • The server never reads message content. Sync endpoints store and return encrypted blobs. The server is not in the message delivery path for P2P chat.
  • The server never carries call media. It relays only the small SDP offer/answer and ICE candidate messages needed to establish the direct WebRTC connection.
  • The mobile app's local database is the primary source of truth for messages. The server holds a synchronized copy for cross-device continuity and history, not as the write-primary.
  • The GSM module is a thin gateway. The SAPOT server decides when to send SMS; the GSM module only translates the HTTP request to AT commands.
  • The admin frontend has no direct database access. All data access goes through the FastAPI server.

See threat-model.md for attack surfaces in scope, trust boundaries, and known risks (insider threat on the LAN, physical access to the router, device theft, E2E encryption design risks).

See deployment/runbooks.md for the fallback procedure if the server hardware fails at an incident site.

TODO (human input required): Document disaster-scenario operating constraints not yet captured elsewhere — expected number of concurrent users and expected geographic area / radio range assumptions.