Open sailing analytics platform for race analysis, session replay, and fleet performance — self-hostable, hardware-agnostic, and built for extensibility.
☕ Support XGSail on Buy Me a Coffee — funds go toward app development, publishing on the app stores (once funding allows), and scaling up servers as usage grows.
XGSail is a fork of SailFrames (originally released as "SailFrames One"). It keeps the original's Apache 2.0 license and general purpose — sailing session analytics — but is not a drop-in copy: the data model, API surface, and frontend have been substantially redesigned rather than incrementally patched (see "Structural differences from upstream" below). The name change reflects that divergence and matches the domain this fork is hosted under.
XGSail is a software-first evolution of the original SailFrames project. It focuses on the application layer: user management, authentication, roles, data ingestion, storage, analytics, and web-based workflows for sailors, coaches, and teams.
XGSail is a platform for collecting, storing, and analyzing sailing session data.
It provides:
- Self-hosted deployment (Postgres + MinIO + backend + frontend + workers, one
docker compose up) - User authentication and role-based access (clubs, groups, boats, per-resource ownership)
- Database-backed storage for users, boats, clubs, groups, devices, sessions, races and regattas
- Session and race analysis workflows (legs, maneuvers, VMG, polar performance)
- Replay and coaching-oriented review tools (track playback, session photos)
- Hardware-agnostic ingestion through a defined protocol (
docs/device-protocol.md) — devices claim themselves, then upload data with a per-device key - Native iOS/Android app shells (Capacitor) around the same frontend, with self-hosted OTA JS-bundle updates (
docs/native-apps.md,docs/ota-updates.md) — still frontend application + deployment configuration scope, not a hardware artifact
The goal is to provide an open platform for sailing analytics that can work with dedicated devices, custom integrations, or external data sources, without tying the application to one specific hardware stack.
XGSail is not the hardware, firmware, or embedded edge stack from the original SailFrames repository.
Those components may integrate with XGSail through the device protocol, but this repository is focused on the software platform and its data contract.
xgsail-e1 is a companion
repository with the firmware and KiCad PCB design for E1, an
ESP32-based fleet tracker built as reference hardware for this
platform: GNSS + IMU + wind + pressure logging, an ESP-NOW peer mesh
for live fleet position sharing and on-course-side (OCS) race-start
detection, and uploads over the device protocol above — direct over
WiFi, or relayed over Bluetooth by the owner's phone when WiFi isn't
available. It's open hardware (Apache 2.0, same license as this repo)
and currently a build-it-yourself project rather than a sold product.
See that repository's README.md and docs/hardware.md for firmware,
schematics, and the BOM.
This repository contains:
- Backend API (
backend/) - Frontend application (
frontend/) - Database models and Alembic migrations (
backend/db/) - Authentication and authorization (
backend/auth/) - Storage and ingestion services (
backend/storage/,backend/services/) - Processing workers (
workers/) - Standalone OTA update server for the native app (
ota-service/) - Protocol and integration documentation (
docs/) - Self-hosted deployment configuration (
deploy/,docker-compose.yml)
XGSail follows a software-platform approach:
- Devices or external tools produce sailing data.
- Data is uploaded using a stable ingestion contract.
- The backend validates, stores, and processes session data.
- The web application exposes analysis, replay, and management features.
This separation allows the platform to evolve independently from any one hardware implementation.
XGSail is under active development. The core platform is implemented and usable end to end:
- Users, authentication, and scoped RBAC (superadmin / club admin / race officer) plus per-resource ownership for boats and groups
- Clubs, groups, boats, and devices, with a claim-flow device protocol for ingestion
- Session import and processing (legs, maneuvers, VMG, polar targets), crew tracking, and photo attachments
- Races, regattas, and race-day management, with a leaderboard
- Multi-language frontend (English/Italian) covering all of the above
Ongoing work is on usability and polish of the existing frontend, and rounding out edge cases in the ingestion and analysis pipeline — not on building the core feature set from scratch.
XGSail is derived from the broader SailFrames effort, but it intentionally narrows the scope to the software application layer.
Where the original project includes hardware, firmware, edge devices, and AWS-oriented infrastructure, XGSail aims to become a cleaner, self-hostable analytics platform with a stable integration surface for present and future devices.
This is a fork in lineage and license, not a rebrand of the same codebase. Concretely:
- Data model: the users/auth/roles/clubs/groups/devices/sessions schema was redesigned from scratch, not incrementally extended from the original tables.
- API surface: routes, permission model, and ingestion contract were rebuilt around that new schema, including a hardware-agnostic device-claim + device-key ingestion flow (
docs/device-protocol.md) replacing the original's device-specific upload path. - Frontend: rebuilt on a simplified page structure and data-fetching approach rather than carried over as-is.
- Scope boundary: firmware, PCB design, and embedded-device internals are excluded entirely — they remain in the upstream hardware repository, not mirrored here even partially.
The redesign these docs specified is now implemented on main. The
retained docs are the source of truth going forward: docs/device-protocol.md
(the ingestion contract) and docs/estimation-pipeline.md (how raw
sensor/API data becomes legs/maneuvers/VMG/polar numbers).
- Open — users can inspect, run, and extend the platform
- Self-hostable — no mandatory vendor lock-in
- Hardware-agnostic — devices integrate through protocols, not tight coupling
- Maintainable — clear boundaries between frontend, backend, storage, and processing
- Extensible — new integrations should not require rewriting the core platform
Everything runs as containers. From the repo root:
docker compose up --buildThen open http://localhost:8080. That's the whole stack:
| Service | Port | What it is |
|---|---|---|
frontend |
8080 | The SPA (nginx). Serves the app and proxies /api → backend, so it's all one origin. |
backend |
8000 | The REST API (FastAPI). Also reachable directly for debugging. |
postgres |
5432 | Metadata (users, boats, sessions, races, RBAC). |
minio |
9000 / 9001 | S3-compatible blob storage (sensor data, video, manifests) + console. |
process_upload, video |
— | Heavy-processing workers (see below). |
No .env is required — every value has a dev default. Copy .env.example to
.env to override secrets for a real deployment.
- One API. The FastAPI
backend/is the only API surface. Metadata lives in Postgres; large files (CSV sensor data, video, JSON results) live in S3/MinIO. - Auth. Email/password (Argon2id) → short-lived JWT access token + rotating
refresh token, per-club RBAC. Dual transport: web uses httpOnly cookies with
double-submit CSRF on mutations; native apps get the same tokens in the
response body and send the access token as an
Authorization: Bearerheader (no cookie, so CSRF doesn't apply). - Workers as microservices.
workers/process_upload(GPS tracks + analysis) andworkers/video(MP4 → HLS via ffmpeg) are built on the AWS Lambda base image. The same image runs on AWS Lambda and locally: in compose it runs via the Lambda Runtime Interface Emulator that ships in the base image, and the backend invokes it over HTTP on a MinIO upload event (/hooks/minio). On AWS the identical image sits behind an S3 → Lambda trigger. No code changes to move between the two.
Repo layout: backend/ · frontend/ · workers/{process_upload,train_maneuver,video}/ ·
ota-service/ (standalone OTA update server) · deploy/ (Dockerfile + MinIO init) ·
scripts/ (migrations, backfills, calibration) · docs/device-protocol.md
(device integration contract) · docs/estimation-pipeline.md (analysis pipeline).
The same frontend also ships as native iOS/Android shells via
Capacitor — not a separate codebase, just the
existing SPA wrapped for things a browser tab can't do (background GPS
recording, Bluetooth device pairing, share-target GPX import). See
docs/native-apps.md for how it's built and docs/ota-updates.md for how
JS/CSS updates reach installs without a store release.
Status: early, not on the official stores.
- Android: buildable and signable, but not published to the Play
Store.
.github/workflows/android-release.ymlbuilds a signed release APK on everyvX.Y.Ztag and attaches it to the matching GitHub Release — install it by sideloading that APK. Aplay-store-uploadjob exists in the same workflow but is disabled. - iOS: not distributable in any form yet, not even for internal
testing —
.github/workflows/ios-release.ymlis a scaffold, disabled.
Why not on the stores yet: cost, not effort. Google Play requires a
one-time $25 registration fee; Apple requires a $99/year Developer
Program membership just to sign a build at all, store or not (see
"Testing without a paid Apple account" in docs/native-apps.md). The
plan is to cover these through donations and publish once that's funded
— sideloading the GitHub Release APK is the path for Android in the
meantime, and iOS has no equivalent until an Apple account is in place.
Apache 2.0, consistent with the original upstream project unless stated otherwise.
