mirror of
https://github.com/renorris/openfsd
synced 2026-08-10 11:33:29 +08:00
docs: sync README and wiki with AFV, cluster, and REST surface
Bring operator and design docs in line with recent landings: -afv, rqlite/FSD mesh, operator REST versioning, and Instructor1+ editor/sweatbox authz. Mark AFV P0/mesh memory path and distributed openfsd as implemented.
This commit is contained in:
110
README.md
110
README.md
@@ -13,28 +13,37 @@ As of May 2025, FSD is still used to facilitate over 140,000 active members conn
|
||||
## Features
|
||||
|
||||
- Multiplayer flight simulation with VATSIM protocol compatibility
|
||||
- Web-based management for users, settings, and connections
|
||||
- SQLite for persistent storage (single file; easy backups)
|
||||
- **Single binary** — FSD and web share one process and one database; enable services with CLI flags
|
||||
- Web-based management: users, config, live connections, account self-service
|
||||
- SQLite for persistent storage (single file; easy backups); optional **rqlite** for multi-node durable state
|
||||
- **Single binary** — FSD, web, and optional AFV share one process and one database; enable services with CLI flags
|
||||
- **Sweatbox** ground/taxi simulator + map-first **airport editor** (`.apt` / `.air`)
|
||||
- **Operator REST** under `/api/v1` with date microversions, discovery, and embedded OpenAPI
|
||||
- **Optional AFV** voice server (`-afv`: REST + UDP CryptoDTO) for TrackAudio / xPilot-shaped clients
|
||||
- **Optional FSD cluster** (rqlite + TCP mesh) for multi-edge deployments
|
||||
|
||||
## Package layout
|
||||
|
||||
```
|
||||
cmd/openfsd/ # Binary entrypoint (FSD + web; image CMD is /openfsd)
|
||||
pkg/protocol/ # Pure wire format (parse/marshal; no I/O)
|
||||
pkg/fsdclient/ # Mock/real FSD client for e2e and tools
|
||||
pkg/twrfiles/ # Pure .apt/.air parse + format (sweatbox + editor)
|
||||
internal/server/ # TCP accept, login, handlers, service HTTP
|
||||
internal/session/ # Per-connection state + outbound send worker
|
||||
internal/postoffice/ # Callsign registry + geospatial index
|
||||
internal/geo/ # Pure haversine / bounding box
|
||||
internal/auth/ # JWT + VATSIM client auth
|
||||
internal/metar/ # METAR worker pool (injectable HTTP)
|
||||
internal/db/ # Shared repositories + migrations
|
||||
internal/serviceapi/ # Pure JSON DTOs for FSD service HTTP (shared by server + web)
|
||||
internal/web/ # Gin MPA + /api/v1
|
||||
internal/sweatbox/ # Ground/taxi sim (stdlib + geo)
|
||||
cmd/aptdat2apt/ # Optional: convert XP12 apt.dat → sweatbox .apt (user-fetched data)
|
||||
cmd/openfsd/ # Binary entrypoint (FSD + web; -afv opt-in)
|
||||
cmd/openfsd-migrate-to-sqlite # Postgres → SQLite row copy
|
||||
cmd/openfsd-migrate-to-rqlite # SQLite file → rqlite HTTP row copy
|
||||
cmd/aptdat2apt/ # Optional: XP12 apt.dat → sweatbox .apt
|
||||
pkg/protocol/ # Pure FSD wire format (parse/marshal; no I/O)
|
||||
pkg/fsdclient/ # Mock/real FSD client for e2e and tools
|
||||
pkg/twrfiles/ # Pure .apt/.air parse + format
|
||||
pkg/afvprotocol/ # Pure AFV CryptoDTO + DTOs + AEAD
|
||||
internal/server/ # TCP accept, login, handlers, service HTTP, FSD mesh
|
||||
internal/session/ # Per-connection state + outbound send worker
|
||||
internal/postoffice/ # Callsign registry + geospatial index
|
||||
internal/geo/ # Pure haversine / bounding box
|
||||
internal/auth/ # JWT + VATSIM client auth
|
||||
internal/metar/ # METAR worker pool (injectable HTTP)
|
||||
internal/db/ # Shared repositories + migrations (sqlite | rqlite)
|
||||
internal/serviceapi/ # Pure JSON DTOs for FSD service HTTP
|
||||
internal/web/ # Gin MPA + progressive enhancement + /api/v1
|
||||
internal/sweatbox/ # Ground/taxi sim (stdlib + geo + twrfiles)
|
||||
internal/cluster/ # FSD mesh framing, claim, directory, interest
|
||||
internal/afv/ # AFV REST + UDP voice + optional AFV mesh
|
||||
```
|
||||
|
||||
### Airport layout data (sweatbox)
|
||||
@@ -51,10 +60,11 @@ Sources, licensing (Gateway / Global Airports GPLv2), and packaging rules:
|
||||
|
||||
### Airport editor (web)
|
||||
|
||||
Administrators can author paired **`.apt`** (geometry) and **`.air`** (scenario
|
||||
Instructors (**Instructor1+**) can author paired **`.apt`** (geometry) and **`.air`** (scenario
|
||||
aircraft) files in the browser at **`/airport-editor`**:
|
||||
|
||||
- Map-first editing (Leaflet; JS required for the map; text paste + echo-download works without JS)
|
||||
- Undo/redo (keyboard; client-only history stack)
|
||||
- **No server persistence** of APT/AIR — open/download only (Blob download with JS; form echo-download without)
|
||||
- Validate tab: live client parse + soft cross-file warnings; optional **Confirm with server**
|
||||
(`POST /api/v1/editor/validate-apt` and `validate-air`)
|
||||
@@ -62,30 +72,49 @@ aircraft) files in the browser at **`/airport-editor`**:
|
||||
|
||||
Design notes: [docs/design/apt-air-editor.md](docs/design/apt-air-editor.md).
|
||||
|
||||
### AFV voice (optional)
|
||||
|
||||
Opt-in with **`-afv`**. Serves AFV REST (auth, callsign session, transceivers) and UDP
|
||||
CryptoDTO voice (H/HA heartbeat, AT→AR radio routing). Not started by default.
|
||||
|
||||
```bash
|
||||
./openfsd -fsd -web -afv # colocated FSD + web + AFV
|
||||
./openfsd -afv # AFV only (shares DATABASE_* with FSD for users)
|
||||
```
|
||||
|
||||
Minimum config: set `AFV_UDP_ADVERTISE_IPV4` (and typically `AFV_API_PUBLIC_BASE_URL`) so
|
||||
clients learn the correct voice endpoint. See [wiki/Configuration](wiki/Configuration.md#afv-voice-optional)
|
||||
and design [docs/design/afv-server.md](docs/design/afv-server.md). Multi-node voice directory
|
||||
relay is in-tree (MemoryMesh tests); production TCP mesh is PR-10b.
|
||||
|
||||
## Build and run
|
||||
|
||||
```bash
|
||||
go build -o openfsd ./cmd/openfsd
|
||||
|
||||
./openfsd # both FSD and web (default)
|
||||
./openfsd # FSD + web (default; AFV remains off)
|
||||
./openfsd -fsd # FSD only (:6809 + service HTTP :13618)
|
||||
./openfsd -web # web only (:8000)
|
||||
./openfsd -afv # AFV voice only (REST + UDP)
|
||||
./openfsd -fsd -web -afv
|
||||
```
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| *(none)* | Both services |
|
||||
| *(none)* | FSD + web (AFV off) |
|
||||
| `-fsd` | FSD only |
|
||||
| `-web` | Web only |
|
||||
| `-fsd -web` | Both (same as default) |
|
||||
| `-afv` | AFV only |
|
||||
| `-fsd -web` | Both data plane + UI (same as default) |
|
||||
| `-fsd -web -afv` | All three services in one process |
|
||||
|
||||
### Environment
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|----------|---------|-------|
|
||||
| `DATABASE_SOURCE_NAME` | `openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)` | SQLite path (preferred) or `:memory:`; must be shared by both services. Bare `:memory:` is rewritten to a process-shared DSN when FSD+web run together. |
|
||||
| `DATABASE_SOURCE_NAME` | `openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)` | SQLite path (preferred) or rqlite HTTP URL; must be shared by colocated services. Bare `:memory:` is rewritten to a process-shared DSN when ≥2 of FSD/web/AFV run together. |
|
||||
| `DATABASE_DRIVER` | `sqlite` | `sqlite` (default) or `rqlite`; Postgres removed (use migrate tools) |
|
||||
| `DATABASE_AUTO_MIGRATE` | `true` | FSD and web apply migrations on startup (idempotent) |
|
||||
| `DATABASE_AUTO_MIGRATE` | `true` | Apply migrations on startup (idempotent; rqlite uses `DATABASE_MIGRATE_LEADER`) |
|
||||
| `FSD_LISTEN_ADDRS` | `:6809` | FSD TCP listen address(es) |
|
||||
| `SERVICE_HTTP_LISTEN_ADDR` | `:13618` | Internal FSD admin HTTP |
|
||||
| `FSD_HTTP_SERVICE_ADDRESS` | `http://127.0.0.1:13618` | Web → FSD service HTTP |
|
||||
@@ -93,6 +122,8 @@ go build -o openfsd ./cmd/openfsd
|
||||
| `LOG_DEBUG` | *(unset)* | Set `true` for slog debug logging (default is info / release) |
|
||||
| `GIN_MODE` | `release` | Gin mode for web + FSD service HTTP; set `debug` for Gin debug output |
|
||||
|
||||
AFV, cluster, and multi-FSD web vars: [wiki/Configuration](wiki/Configuration.md).
|
||||
|
||||
Colocated mode (default) uses the shared DB and in-process service HTTP. For `-web` against a remote FSD, set `FSD_HTTP_SERVICE_ADDRESS`.
|
||||
|
||||
## Quick start (Docker)
|
||||
@@ -101,12 +132,14 @@ Preferred for operators. See the [Deployment Wiki](https://github.com/renorris/o
|
||||
|
||||
Images: **`ghcr.io/renorris/openfsd`** — CI publishes `:latest` from **`main` only**, `:dev` from **`dev`** (unstable), plus `sha-*` / branch tags.
|
||||
|
||||
**Upgrading from PostgreSQL?** Use [`openfsd-migrate-to-sqlite`](cmd/openfsd-migrate-to-sqlite) and [Migrating from PostgreSQL](wiki/Migrating-from-PostgreSQL.md). Optional multi-node: rqlite + mesh (`CLUSTER_ENABLED`, see [wiki/Deployment](wiki/Deployment.md) and `docs/design/distributed-openfsd.md`).
|
||||
**Upgrading from PostgreSQL?** Use [`openfsd-migrate-to-sqlite`](cmd/openfsd-migrate-to-sqlite) and [Migrating from PostgreSQL](wiki/Migrating-from-PostgreSQL.md).
|
||||
|
||||
**Optional multi-node:** rqlite + FSD mesh (`CLUSTER_ENABLED`, see [wiki/Deployment](wiki/Deployment.md) and `docs/design/distributed-openfsd.md`). Sample: `docker-compose.cluster.yml`.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/renorris/openfsd.git
|
||||
cd openfsd
|
||||
docker compose up -d # pull/build single image; both services
|
||||
docker compose up -d # pull/build single image; FSD + web
|
||||
# or: docker compose up -d --build
|
||||
```
|
||||
|
||||
@@ -118,7 +151,7 @@ docker compose up -d # pull/build single image; both services
|
||||
### Service selection
|
||||
|
||||
```bash
|
||||
# Both (default CMD)
|
||||
# FSD + web (default CMD)
|
||||
docker run --rm -p 6809:6809 -p 8000:8000 ghcr.io/renorris/openfsd:latest
|
||||
|
||||
# FSD only
|
||||
@@ -128,6 +161,13 @@ docker run --rm -p 6809:6809 ghcr.io/renorris/openfsd:latest /openfsd -fsd
|
||||
docker run --rm -p 8000:8000 \
|
||||
-e FSD_HTTP_SERVICE_ADDRESS=http://fsd-host:13618 \
|
||||
ghcr.io/renorris/openfsd:latest /openfsd -web
|
||||
|
||||
# AFV only (example ports; set advertise + public base URL for real clients)
|
||||
docker run --rm -p 8080:8080 -p 50000:50000/udp \
|
||||
-e AFV_API_LISTEN=0.0.0.0:8080 \
|
||||
-e AFV_UDP_ADVERTISE_IPV4=host.example:50000 \
|
||||
-e AFV_API_PUBLIC_BASE_URL=https://voice.example \
|
||||
ghcr.io/renorris/openfsd:latest /openfsd -afv
|
||||
```
|
||||
|
||||
### Local smoke
|
||||
@@ -144,16 +184,22 @@ docker compose down
|
||||
```bash
|
||||
go test -race ./...
|
||||
bash scripts/check-coverage.sh 80 # overall ≥80%; pure-pkg floors (see AGENTS.md)
|
||||
bash scripts/check-webjs.sh # Node ≥20 unit tests for airport-editor JS modules
|
||||
bash scripts/check-webjs.sh # Node ≥20 (CI pins 24) airport-editor JS modules
|
||||
go test -bench=. -benchmem ./internal/postoffice/ ./pkg/protocol/
|
||||
go test -tags=stress -count=1 -timeout=120s ./internal/server/ -run TestStress -v
|
||||
```
|
||||
|
||||
E2E: `internal/server/e2e_test.go` via `pkg/fsdclient` + `StartTestServer`. Stress is optional (CI schedule / `workflow_dispatch`).
|
||||
E2E: `internal/server` via `pkg/fsdclient` + `StartTestServer`. Stress is optional (CI schedule / `workflow_dispatch`).
|
||||
|
||||
## API
|
||||
|
||||
`/api/v1` covers auth, users, config, and FSD connections. See [internal/web](https://github.com/renorris/openfsd/tree/main/internal/web).
|
||||
Operator automation under **`/api/v1`**: versioned discovery, users, config, FSD connections, sweatbox control, editor validate, and provisional account self-service.
|
||||
|
||||
- Operator guide + authz matrix: [`internal/web/README.md`](internal/web/README.md)
|
||||
- OpenAPI (embedded): `GET /api/v1/openapi.yaml` or `internal/web/openapi/openapi.v1.yaml`
|
||||
- Design: [docs/design/rest-api-versioning.md](docs/design/rest-api-versioning.md)
|
||||
|
||||
Send `OpenFSD-API-Version: YYYY-MM-DD` on production resource calls (see the web README).
|
||||
|
||||
## Protocol docs
|
||||
|
||||
@@ -163,3 +209,7 @@ Unofficial reverse-engineered FSD protocol docs live under `docs/`:
|
||||
pip install mkdocs
|
||||
mkdocs serve
|
||||
```
|
||||
|
||||
## Operator wiki
|
||||
|
||||
Tracked source for the GitHub wiki is under [`wiki/`](wiki/) (Deployment, Configuration, Client Connection, Postgres migration).
|
||||
|
||||
@@ -5,12 +5,21 @@
|
||||
| **Title** | AFV multi-node mesh — directory sync + AT frame relay |
|
||||
| **Author** | openfsd design (implementer-ready) |
|
||||
| **Date** | 2026-07-28 |
|
||||
| **Status** | Draft (rev 3 — hard testing bar + residual review) |
|
||||
| **Status** | **Memory path Implemented** (PR-10 on `dev`: framing, Interest, AudioRelay, MemoryMesh e2e Cases A–F, fail-closed `ENABLED` without TCP). **TCP mesh = PR-10b** (not in production binary). |
|
||||
| **Parent** | `docs/design/afv-server.md` (Clustering, KD-8/9/16/17, PR-10) |
|
||||
| **Contract** | `docs/design/afv-mesh-implementer-prompt.md` |
|
||||
|
||||
---
|
||||
|
||||
## Implementation status (as of 2026-07-28)
|
||||
|
||||
| Item | State |
|
||||
|------|--------|
|
||||
| Framing, Interest, remote directory, AudioRelay, MemoryMesh e2e | **Landed** (`internal/afv/mesh_*.go`, Cases A–F) |
|
||||
| Fail-closed `AFV_CLUSTER_ENABLED` without TCP | **Landed** |
|
||||
| Production `TCPMesh` | **PR-10b** — not in binary; enable fails with clear error |
|
||||
| Operator wiki for multi-node AFV | Single-node ops documented; multi-host voice deferred until TCP |
|
||||
|
||||
## Overview
|
||||
|
||||
P0 openfsd AFV is a single-node REST + UDP CryptoDTO voice service (`internal/afv`). Multi-region or multi-host voice requires two or more AFV processes to hear each other without shipping client AEAD keys off-node and without putting Opus into rqlite.
|
||||
|
||||
@@ -5,15 +5,26 @@
|
||||
| **Document** | AFV (Audio for VATSIM) server — full feature design |
|
||||
| **Author** | _(design author / implementer)_ |
|
||||
| **Date** | 2026-07-28 |
|
||||
| **Status** | **Draft** (rev 2 — review e59fdb24 addressed) |
|
||||
| **Status** | **P0 Implemented** (single-node REST + UDP on `dev`; design history retained). Mesh: MemoryMesh path landed (PR-10); production TCP = PR-10b. |
|
||||
| **Project** | openfsd |
|
||||
| **Target land path** | `docs/design/afv-server.md` (when accepted) |
|
||||
| **Target land path** | `docs/design/afv-server.md` |
|
||||
| **Related** | `Agents.md`, `docs/design/distributed-openfsd.md`, `internal/auth`, `internal/geo`, `internal/cluster`, `internal/db`, `cmd/openfsd/main.go`, [AFV-Native](https://github.com/xsquawkbox/AFV-Native) (BSD-3) |
|
||||
| **Protocol source of truth** | AFV-Native headers/sources (Chris Collins); also used by TrackAudio, xPilot, VectorAudio, vPilot (VATSIM clients) |
|
||||
| **Revision** | rev 2: JWT/`internal/auth` alignment; normative HA recipe; bootstrap; mesh/XC/concurrency/coverage/PR-plan fixes |
|
||||
|
||||
---
|
||||
|
||||
## Implementation status (as of 2026-07-28)
|
||||
|
||||
| Slice | State |
|
||||
|-------|--------|
|
||||
| P0 single-node (`pkg/afvprotocol`, `internal/afv`, `-afv`) | **Landed** — REST auth/callsign/transceivers; UDP H/HA + AT→AR; range model; reaper; empty stations route |
|
||||
| PR-10 mesh (MemoryMesh + framing + Interest + AudioRelay) | **Landed** (in-process / test mesh; e2e Cases A–F) |
|
||||
| PR-10b production TCP mesh | **Not landed** — `AFV_CLUSTER_ENABLED=true` fails closed without TCP in binary |
|
||||
| Operator docs | Wiki Configuration/Deployment AFV sections; root README |
|
||||
|
||||
Design body below is the engineering record (decisions, protocol, PR plan). Prefer wiki + `internal/afv` for day-to-day ops.
|
||||
|
||||
## Overview
|
||||
|
||||
openfsd today is an FSD data-plane + boring-web control plane. Clients (vPilot, xPilot, TrackAudio, AFV-Native) obtain **voice** from a completely separate service: **Audio for VATSIM (AFV)**. AFV is a two-plane system:
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
> **Closeout (2026-07-27):** Code landed in `history.js` / `main.js` with pure tests under `webjs/airport-editor/history.test.js`. Keyboard-only undo/redo (`Mod+Z` / `Mod+Shift+Z` / `Mod+Y` when `!inField`), gesture-coalesced vertex/aircraft drag, snapshot stack (max depth 100), and K13 clean-content hash seeding at bootstrap / successful Open / New. Design history below is retained as the engineering record (key decisions, mutation inventory, checklist). Parent dirty-hash baseline prose retconned in [`apt-air-editor.md`](apt-air-editor.md).
|
||||
|
||||
The openfsd **Airport Editor** (`/airport-editor`) is a map-first, Admin-only progressive-enhancement tool for authoring paired `.apt` / `.air` documents entirely in the browser. Geometry, headers, and aircraft are mutated in place via `model.js` helpers. Before this work there was **no** undo stack: a mis-dragged vertex, accidental surface delete, or botched raw Apply was permanent until the user re-opened a file.
|
||||
The openfsd **Airport Editor** (`/airport-editor`) is a map-first, Instructor1+ progressive-enhancement tool for authoring paired `.apt` / `.air` documents entirely in the browser. Geometry, headers, and aircraft are mutated in place via `model.js` helpers. Before this work there was **no** undo stack: a mis-dragged vertex, accidental surface delete, or botched raw Apply was permanent until the user re-opened a file.
|
||||
|
||||
This design adds a **pure, Node-testable history module** and thin wiring in `main.js` so every **committed** document mutation is undoable/redoable via keyboard shortcuts. History is **snapshot-based** (deep clones of airport + aircraft + selection), with **gesture coalescing** so a vertex/aircraft drag is one undo step, not one step per pointer move. Dirty tracking continues to use FNV clean-content hashes (`last*DownloadHash`, K13); download itself is not an undo entry. **No toolbar undo/redo buttons** — keyboard only. No server persistence of history.
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ Shipped. Summary of the tree as of closeout:
|
||||
|------|------------------|
|
||||
| Parse + Format | `pkg/twrfiles` (`ParseAPT`/`ParseAIR`, `FormatAPT`/`FormatAIR`, fixtures under `pkg/twrfiles/testdata`) |
|
||||
| Sweatbox | Thin aliases/wrappers over `pkg/twrfiles` (engine still pure) |
|
||||
| Editor MPA | `/airport-editor` shell + Leaflet PE; echo-download; Admin-only |
|
||||
| Validate API | `POST /api/v1/editor/validate-apt` / `validate-air` via `pkg/twrfiles` |
|
||||
| Editor MPA | `/airport-editor` shell + Leaflet PE; echo-download; **Instructor1+** (product KD-5; was Admin-only in original design) |
|
||||
| Validate API | `POST /api/v1/editor/validate-apt` / `validate-air` via `pkg/twrfiles` (**Instructor1+**) |
|
||||
| Web JS | Pure modules under `internal/web/static/js/openfsd/`; Node tests in `webjs/` + `scripts/check-webjs.sh` in CI |
|
||||
| Persistence | **None** durable (no disk/DB); Blob download + transient request bodies only |
|
||||
| Playwright | **Cancelled** — no browser automation suite |
|
||||
@@ -35,7 +35,7 @@ Design history below is retained. Stale “Format missing / JS tests none” row
|
||||
|
||||
openfsd already loads TWRTrainer-compatible **`.apt`** (airport geometry) and **`.air`** (scenario aircraft snapshots) into an in-process sweatbox simulator and exposes an instructor control panel at `/sweatbox`. Instructors can also author those files in-product via the integrated map editor.
|
||||
|
||||
This design delivers a **single, integrated map-first editor** on the openfsd admin Web UI for creating and editing **paired** `.apt` and `.air` files. The editor is a **server-rendered MPA shell** with a **JavaScript-heavy progressive enhancement** (Leaflet map + geometry tools). **No durable server persistence** of `.apt` / `.air` (no disk, no DB): primary load is client-local (FileReader / paste) and primary save is **browser Blob download**. Optional Admin-only echo-download and validate POSTs may hold bodies **transiently in process memory** for the duration of the request only — they never write files or store rows.
|
||||
This design delivers a **single, integrated map-first editor** on the openfsd admin Web UI for creating and editing **paired** `.apt` and `.air` files. The editor is a **server-rendered MPA shell** with a **JavaScript-heavy progressive enhancement** (Leaflet map + geometry tools). **No durable server persistence** of `.apt` / `.air` (no disk, no DB): primary load is client-local (FileReader / paste) and primary save is **browser Blob download**. Optional Instructor1+ echo-download and validate POSTs may hold bodies **transiently in process memory** for the duration of the request only — they never write files or store rows.
|
||||
|
||||
The page targets a dense **16:9 operator console** matching the sweatbox rail aesthetic, reuses in-tree Leaflet assets, and respects the boring-web house standard and the web ↛ sweatbox import-graph edge.
|
||||
|
||||
@@ -94,7 +94,7 @@ Airport geometry is inherently geographic. A text-only form would satisfy “no
|
||||
1. **Integrated .apt + .air editor** on one bookmarkable URL, map canvas + dense rail.
|
||||
2. **OpenStreetMap base map** with airport-intuitive layering (documented tile choice).
|
||||
3. **Utilitarian 16:9 console** consistent with sweatbox visual language (tokens, panels, mono numbers).
|
||||
4. **No durable server persistence** of APT/AIR (disk/DB); document lives in the browser; download only. Transient request bodies allowed for echo-download and validate (Admin-only, size-capped).
|
||||
4. **No durable server persistence** of APT/AIR (disk/DB); document lives in the browser; download only. Transient request bodies allowed for echo-download and validate (Instructor1+, size-capped).
|
||||
5. **Boring-web compliance**: real route, server authz, no SPA router, no global server-state store, CSRF only when mutating the server. Map geometry authoring is a **documented complexity-gate exception** (like dashboard Leaflet); no-JS paste + echo-download remains for the data path.
|
||||
6. **JS heavily tested**: unit coverage as close to 100% as practical on pure modules; e2e covering editor flows.
|
||||
7. **Go tests** for any new routes/handlers (authz, shell render, optional echo-download).
|
||||
@@ -286,7 +286,7 @@ Also update `Agents.md` §1 package table: sweatbox = stdlib + `internal/geo` +
|
||||
|------|--------|
|
||||
| **URL** | `GET /airport-editor` |
|
||||
| **Auth** | Session cookie (`requireSessionHTML`) |
|
||||
| **Authz** | **Administrator** (`requireMinRatingHTML(protocol.NetworkRatingAdministator)`) — same tier as `/sweatbox` and config editor |
|
||||
| **Authz** | **Instructor1+** (`requireMinRatingHTML(protocol.NetworkRatingInstructor1)`) — same tier as `/sweatbox`; config editor remains Administrator |
|
||||
| **Nav** | Layout header: link **Airport Editor** next to Sweatbox when `User.CanEditConfig` |
|
||||
| **Does not require** | `SWEATBOX_ENABLED` or FSD process |
|
||||
|
||||
@@ -861,7 +861,7 @@ Content-Disposition: attachment; filename="airport.apt"
|
||||
- No `os.WriteFile`, `os.Create`, `os.OpenFile` for user content; no DB repository calls.
|
||||
- Go tests: snapshot workdir (or `t.TempDir` as cwd if needed) before/after — file set unchanged.
|
||||
- `slog` allowlist: `cid`, `content_length`, `icao` (if parsed), `error_count`, `path` — **never** full body text.
|
||||
- Transient RAM only for request lifetime; Admin-only so body-in-RAM is accepted risk.
|
||||
- Transient RAM only for request lifetime; Instructor1+ so body-in-RAM is accepted risk.
|
||||
|
||||
Default filename: from form field `filename` if safe (`^[A-Za-z0-9._-]{1,64}$`), else `airport.apt` / `scenario.air`. Prefer ICAO when parse succeeds: `KBTV.apt`. **Do not block download on validation errors** (WIP authoring); optional `strict=1` may reject later — not v1 default.
|
||||
|
||||
@@ -880,7 +880,7 @@ api.POST("/validate-air", s.handleAPIValidateAIR)
|
||||
| Condition | Response |
|
||||
|-----------|----------|
|
||||
| Unauthenticated | Existing `jwtBearerMiddleware` behavior (401 JSON) |
|
||||
| Authenticated but rating < Administrator | **`403`** + `writeAPIV1Response(c, http.StatusForbidden, &genericAPIV1Forbidden)` — same as `handleAPISweatboxState` / `api_tokens.go` |
|
||||
| Authenticated but rating < Instructor1 | **`403`** + `writeAPIV1Response(c, http.StatusForbidden, &genericAPIV1Forbidden)` — same as sweatbox JSON proxies |
|
||||
| Never | `303` redirect to `/dashboard` (that is **HTML-only** middleware) |
|
||||
|
||||
**Do not** introduce `requireMinRatingAPI` that wraps `requireMinRatingHTML`. Prefer **inline rating check** after claims (copy sweatbox API pattern). If a shared helper is added later, it must return JSON 403 only and must not redirect.
|
||||
@@ -966,7 +966,7 @@ webjs/
|
||||
format-air.test.js
|
||||
model.test.js
|
||||
…
|
||||
README.md # how to run; Node 20 LTS pin
|
||||
README.md # how to run; Node ≥20 floor (CI pins 24)
|
||||
```
|
||||
|
||||
```text
|
||||
@@ -997,7 +997,7 @@ cd "$(dirname "$0")/../webjs"
|
||||
node --test airport-editor/**/*.test.js
|
||||
```
|
||||
|
||||
- Pin **Node 24** in CI (`actions/setup-node@v7` with `node-version: "24"`; local floor ≥20).
|
||||
- Pin **Node 24** in CI (`actions/setup-node@v7` with `node-version: "24"`; local floor ≥20). *(Landed: CI workflow uses Node 24.)*
|
||||
- Prefer **zero npm deps** for unit tests (`node:test` only). If `package.json` scripts only, `npm test` can invoke node without `node_modules`.
|
||||
- Add `node_modules/` to `.gitignore` if any package is ever added.
|
||||
- Update `Agents.md` §9 and CI workflow in **PR 4**.
|
||||
@@ -1124,10 +1124,10 @@ Update `scripts/check-import-graph.sh`: `check_stdlib_only` for `pkg/twrfiles`;
|
||||
| Open redirect | N/A |
|
||||
| Tile server data leakage | Only lat/lon tile coords leave browser to OSM/Esri — expected for maps |
|
||||
| Sensitive scenario data in localStorage | v1: do not persist full drafts |
|
||||
| Validate API as parse oracle | Admin-only; rate not critical; still size-capped |
|
||||
| Validate API as parse oracle | Instructor1+; rate not critical; still size-capped |
|
||||
| Content-Disposition injection | Sanitize filename (no CR/LF/quotes) |
|
||||
| Accidental durable write | Handler criteria: no file/DB APIs; PE tests assert workdir unchanged |
|
||||
| Body in RAM (echo/validate) | Admin-only + 2 MiB cap; transient request scope only — **not** a persistence path |
|
||||
| Body in RAM (echo/validate) | Instructor1+ + 2 MiB cap; transient request scope only — **not** a persistence path |
|
||||
|
||||
**Persistence wording (airtight):**
|
||||
|
||||
@@ -1290,7 +1290,7 @@ bash scripts/check-webjs.sh
|
||||
| 5 | PR6: read-only map; PR7: edit + Blob; PR8: validation polish |
|
||||
| 6 | ~~PR9 Playwright~~ **cancelled** — docs only; no browser automation |
|
||||
|
||||
**Feature flag:** not required — Admin-only page is the gate. If needed, env `AIRPORT_EDITOR_ENABLED=false` can hide route; **default on** once shipped.
|
||||
**Feature flag:** not required — Instructor1+ page authz is the gate. If needed, env `AIRPORT_EDITOR_ENABLED=false` can hide route; **default on** once shipped.
|
||||
|
||||
**Rollback:** revert PRs; no DB migration. Static embed reverts with binary.
|
||||
|
||||
@@ -1300,7 +1300,7 @@ bash scripts/check-webjs.sh
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Should Supervisors access the editor?** **Resolved for v1:** Admin-only (product default; Supervisor access deferred).
|
||||
1. **Should Supervisors / instructors access the editor?** **Resolved:** **Instructor1+** (aligned with sweatbox; see user-dashboard KD-5). Original v1 draft was Admin-only.
|
||||
2. **Esri imagery ToS** for each deployer's traffic profile — keep optional and documented.
|
||||
3. **Undo stack?** **Shipped** — keyboard-only snapshot history; see `docs/design/airport-editor-undo-redo.md`. (Earlier “deferred v1.1” superseded.)
|
||||
4. **~~Playwright CI~~** — **cancelled.** No browser automation suite.
|
||||
@@ -1314,7 +1314,7 @@ Resolved by this rev: WIP download allowed (yes); fixture path = `pkg/twrfiles/t
|
||||
| # | Decision | Rationale |
|
||||
|---|----------|-----------|
|
||||
| 1 | **Single page `/airport-editor`**, not nested under live sweatbox | Independent of `SWEATBOX_ENABLED`; clear authoring vs control-plane split |
|
||||
| 2 | **Admin-only** (same as `/sweatbox`) | Consistent training-ops audience; tight authz |
|
||||
| 2 | **Instructor1+** (same as `/sweatbox`; product update from Admin-only) | Training-ops audience; instructors author layouts |
|
||||
| 3 | **No durable server persistence**; Blob primary; transient echo/validate only | User requirement; RAM OK for Admin-sized POSTs |
|
||||
| 4 | **Optional form echo-download** for no-JS | Boring-web data path without map |
|
||||
| 5 | **`pkg/twrfiles` stdlib package** + **sweatbox type aliases/wrappers in PR1** | Web cannot import sweatbox; sim blast radius controlled; Format missing today |
|
||||
|
||||
@@ -5,17 +5,29 @@
|
||||
| **Document** | Distributed multi-node openfsd (mesh + rqlite) |
|
||||
| **Author** | _(design author / implementer)_ |
|
||||
| **Date** | 2026-07-28 |
|
||||
| **Status** | **Draft** (rev 4 — product decisions: full mesh path, synthetic #DP, FPL cache) |
|
||||
| **Status** | **Implemented (opt-in)** on `dev` — rqlite driver, FSD TCP mesh, `docker-compose.cluster.yml`, migrate-to-rqlite, wiki Deployment/Configuration. Single-node SQLite remains default. Design history retained. |
|
||||
| **Project** | openfsd |
|
||||
| **Target land path** | `docs/design/distributed-openfsd.md` (when accepted) |
|
||||
| **Target land path** | `docs/design/distributed-openfsd.md` |
|
||||
| **Related** | `Agents.md`, `internal/postoffice`, `internal/server/deps.go`, `internal/db/*`, `internal/serviceapi`, `docs/design/sweatbox-integrated-simulator.md`, `wiki/Deployment.md`, rqlite docs |
|
||||
| **Revision** | rev 4: product G0=full mesh; synthetic #DP on peer death; FPL cache+RPC miss; ≤8 peers; ring reconfig remains ops-drain v1 |
|
||||
|
||||
---
|
||||
|
||||
## Implementation status (as of 2026-07-28)
|
||||
|
||||
| Slice | State |
|
||||
|-------|--------|
|
||||
| rqlite driver + migrate tool | **Landed** (`DATABASE_DRIVER=rqlite`, `cmd/openfsd-migrate-to-rqlite`) |
|
||||
| FSD TCP mesh (claim, directory, interest, HomeRPC) | **Landed** (`internal/cluster`, `CLUSTER_*`) |
|
||||
| Sample multi-node compose | **Landed** (`docker-compose.cluster.yml`) |
|
||||
| Operator wiki | **Landed** (Deployment multi-node section, Configuration cluster env) |
|
||||
| Default path | Still **single-node SQLite** (`CLUSTER_ENABLED=false`) |
|
||||
|
||||
Design body below retains the full plan and feasibility report.
|
||||
|
||||
## Overview
|
||||
|
||||
openfsd today is a **single-process, single-node** FSD stack: gnet TCP plane, in-process `postoffice` registry (lock-free O(N) slab scan, VATSIM-scale ≤~15k), session-local flight plans, SQLite for users/config, and a boring-web control plane that talks to FSD only via service HTTP. That design is correct and fast for club / VA / regional networks. It does **not** give VATSIM-style properties: clients cannot pin to a geographically nearest edge, formation flying across distant peers cannot exploit local RTT, and a node death takes the whole network offline.
|
||||
openfsd’s **default** is a **single-process, single-node** FSD stack: gnet TCP plane, in-process `postoffice` registry (lock-free O(N) slab scan, VATSIM-scale ≤~15k), session-local flight plans, SQLite for users/config, and a boring-web control plane that talks to FSD only via service HTTP. That design is correct and fast for club / VA / regional networks. Without the optional cluster path it does **not** give VATSIM-style properties: clients cannot pin to a geographically nearest edge, formation flying across distant peers cannot exploit local RTT, and a node death takes the whole network offline.
|
||||
|
||||
This document is a **full implementation plan plus an honest feasibility/rationality report** for making openfsd **optionally distributed**:
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
| **Document** | Operator REST surface expansion + durable versioning/compatibility |
|
||||
| **Author** | _(design author / implementer)_ |
|
||||
| **Date** | 2026-07-28 |
|
||||
| **Status** | **Accepted** (design review consensus 2026-07-28) |
|
||||
| **Status** | **Accepted + Implemented** (design review 2026-07-28; middleware, discovery, OpenAPI embed, first-train routes, goldens, operator guide in `internal/web/README.md`) |
|
||||
| **Project** | openfsd |
|
||||
| **Target land path** | `docs/design/rest-api-versioning.md` |
|
||||
| **Related** | `internal/web/README.md`, `Agents.md` §2/§6, `docs/design/user-dashboard-self-service.md`, `docs/design/sweatbox-integrated-simulator.md`, `internal/serviceapi/*` |
|
||||
|
||||
@@ -18,5 +18,15 @@ See [openfsd](https://github.com/renorris/openfsd) for a free and open-source FS
|
||||
| Enumerations | [enumerations.md](enumerations.md) |
|
||||
| FSD JWT login tokens | [authentication-token.md](authentication-token.md) |
|
||||
| In-band client authenticity (`$ZC`/`$ZR`) | [vatsim-auth.md](vatsim-auth.md) |
|
||||
| X-Plane airport data (sweatbox layouts) | [xplane-airport-data.md](xplane-airport-data.md) |
|
||||
|
||||
## Related openfsd docs (outside this protocol set)
|
||||
|
||||
| Topic | Location |
|
||||
|-------|----------|
|
||||
| Operator wiki (deploy / config / clients) | Repository [`wiki/`](../wiki/) |
|
||||
| Operator REST + OpenAPI | [`internal/web/README.md`](../internal/web/README.md) |
|
||||
| Design notes (AFV, cluster, sweatbox, editor) | [`docs/design/`](design/) |
|
||||
| Contributor package rules | [`AGENTS.md`](../AGENTS.md) |
|
||||
|
||||
*This documentation is an independent work and is not affiliated with, endorsed by, or associated with VATSIM, Inc.*
|
||||
|
||||
@@ -44,3 +44,16 @@ See [vPilot Patch Utility](https://github.com/renorris/vpilot-patch-utility)
|
||||
## xPilot
|
||||
|
||||
See [here](https://github.com/renorris/openfsd-client-patch-utility).
|
||||
|
||||
## Voice (AFV)
|
||||
|
||||
FSD data and **Audio for VATSIM (AFV)** voice are separate attachment planes. openfsd can serve AFV when started with **`-afv`** (see [Configuration](Configuration.md#afv-voice-optional) and [Deployment](Deployment.md#optional-afv-voice)).
|
||||
|
||||
Clients such as **TrackAudio**, **xPilot**, and **VectorAudio** (AFV-Native-derived) need:
|
||||
|
||||
1. FSD host/port (or private-server entry) as usual
|
||||
2. A **voice base URL** pointing at the AFV REST API (`AFV_API_PUBLIC_BASE_URL` / your published HTTPS URL)
|
||||
|
||||
AFV authenticates against the same certificate database as FSD (CID + password). UDP voice endpoints are advertised in the AFV channel config from `AFV_UDP_ADVERTISE_IPV4` (must be reachable by clients, including through NAT).
|
||||
|
||||
Without `-afv`, use an external voice solution (Discord, etc.); FSD multiplayer still works independently of voice.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Persistent configuration (database)
|
||||
|
||||
Some settings are stored in the SQLite database and shared by FSD and web when they use the same file.
|
||||
Some settings are stored in the SQLite (or rqlite) database and shared by FSD, web, and AFV when they use the same DSN.
|
||||
|
||||
**Important values to set after first boot:**
|
||||
|
||||
@@ -25,7 +25,7 @@ Use the web UI (**Configure Server**) to set these.
|
||||
|
||||
| Name | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `DATABASE_SOURCE_NAME` | SQLite DSN (file path preferred) **or** rqlite HTTP base URL when `DATABASE_DRIVER=rqlite` (e.g. `http://rqlite:4001`). Bare `:memory:` is private per connection; when both services run in one process it is rewritten to a shared in-memory DSN. | `openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)` |
|
||||
| `DATABASE_SOURCE_NAME` | SQLite DSN (file path preferred) **or** rqlite HTTP base URL when `DATABASE_DRIVER=rqlite` (e.g. `http://rqlite:4001`). Bare `:memory:` is private per connection; when ≥2 of FSD/web/AFV run in one process it is rewritten to a shared in-memory DSN. | `openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)` |
|
||||
| `DATABASE_DRIVER` | `sqlite` (default) or `rqlite`. Legacy `postgres` fails startup — see [Migrating from PostgreSQL](Migrating-from-PostgreSQL.md). | `sqlite` |
|
||||
| `DATABASE_AUTO_MIGRATE` | When `true`, apply migrations on startup. For rqlite, only the process with `DATABASE_MIGRATE_LEADER=true` migrates. | `true` |
|
||||
| `DATABASE_MIGRATE_LEADER` | When `true` with rqlite, this process runs schema migrations (exactly one leader). | `false` |
|
||||
@@ -68,12 +68,60 @@ Default is **off**. See [Deployment](Deployment.md#optional-multi-node-cluster)
|
||||
| `FSD_HTTP_SERVICE_ADDRESS` | Base URL of the FSD internal HTTP API. Default assumes colocated FSD. | `http://127.0.0.1:13618` |
|
||||
| `FSD_HTTP_SERVICE_ADDRESSES` | Multi-FSD list: bare URLs or `nodeID=http://host:port` pairs (kick routing + online_users aggregate) | *(empty → single address)* |
|
||||
| `COOKIE_SECURE` | `true` / `false` force Secure cookies; empty derives from TLS / `X-Forwarded-Proto` | *(empty)* |
|
||||
| `ALLOW_PERMANENT_ACCOUNT_DELETE` | When `true`, account self-service may hard-delete; default soft-delete only | `false` |
|
||||
|
||||
### AFV voice (optional)
|
||||
|
||||
Enabled only when the process is started with **`-afv`**. Shares `DATABASE_*` with FSD/web for certificate auth. Design: `docs/design/afv-server.md`.
|
||||
|
||||
| Name | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `AFV_API_LISTEN` | REST API listen address | `127.0.0.1:8080` |
|
||||
| `AFV_API_PUBLIC_BASE_URL` | Public base URL clients use for REST (scheme + host) | *(empty)* |
|
||||
| `AFV_UDP_LISTEN` | UDP CryptoDTO listen address | `0.0.0.0:50000` |
|
||||
| `AFV_UDP_ADVERTISE_IPV4` | **Required to start UDP** — host:port embedded in channel config for clients | *(empty)* |
|
||||
| `AFV_UDP_ADVERTISE_IPV6` | Optional IPv6 advertise host:port | *(empty)* |
|
||||
| `AFV_TLS_CERT_FILE` / `AFV_TLS_KEY_FILE` | TLS for REST (prefer in production) | *(empty → HTTP)* |
|
||||
| `AFV_JWT_SECRET` | Override JWT secret; else shared config KV `JWT_SECRET_KEY` | *(empty)* |
|
||||
| `AFV_JWT_TTL` | AFV bearer token lifetime | `1h` |
|
||||
| `AFV_HEARTBEAT_TIMEOUT` | UDP heartbeat timeout | `10s` |
|
||||
| `AFV_SESSION_IDLE_TIMEOUT` | Idle session reaper | `30s` |
|
||||
| `AFV_MAX_SESSIONS` | Global session cap | `5000` |
|
||||
| `AFV_MAX_SESSIONS_PER_CID` | Per-CID session cap | `5` |
|
||||
| `AFV_AUTH_FAIL_MAX` / `AFV_AUTH_FAIL_WINDOW` | Auth brute-force window | `20` / `1m` |
|
||||
| `AFV_MAX_DATAGRAM` | Reject larger UDP frames before decrypt | `8192` |
|
||||
| `AFV_REQUIRE_FSD_ONLINE` | Gate voice on FSD online_users | `false` |
|
||||
| `AFV_FSD_HTTP_SERVICE_ADDRESS` | Service HTTP for online gate | `http://127.0.0.1:13618` |
|
||||
| `AFV_CALLSIGN_STRICT` | Reject replace on callsign conflict (default allows replace) | `false` |
|
||||
| `AFV_RANGE_UNICOM_NM` | Unicom max range (NM) | `15` |
|
||||
| `AFV_RANGE_DEFAULT_NM` | Default pilot max range (NM) | `40` |
|
||||
| `AFV_RANGE_ATC_NM` | ATC max range (NM) | `150` |
|
||||
| `AFV_RANGE_EDGE_RATIO` | Range edge ratio for volume falloff | `0.1` |
|
||||
| `AFV_STATIONS_FILE` | Optional station alias file (empty → `[]` aliases) | *(empty)* |
|
||||
| `AFV_CROSS_COUPLE` | Multi-freq ATC cross-coupling | `true` |
|
||||
| `AFV_CLUSTER_ENABLED` | AFV multi-node mesh | `false` |
|
||||
| `AFV_CLUSTER_NODE_ID` | AFV mesh node id | *(required when enabled)* |
|
||||
| `AFV_CLUSTER_LISTEN` | AFV mesh listen | *(required when enabled)* |
|
||||
| `AFV_CLUSTER_PEERS` | `id=host:port,...` (max 4 remote) | *(required when enabled)* |
|
||||
| `AFV_CLUSTER_PSK` | AFV mesh shared secret | *(required when enabled)* |
|
||||
|
||||
**Note:** `AFV_CLUSTER_ENABLED=true` currently **fails closed** without production TCP mesh in the binary (MemoryMesh is tests-only). See [Deployment](Deployment.md#optional-afv-voice).
|
||||
|
||||
Minimal single-node AFV example:
|
||||
|
||||
```text
|
||||
AFV_API_LISTEN=0.0.0.0:8080
|
||||
AFV_API_PUBLIC_BASE_URL=https://voice.example.com
|
||||
AFV_UDP_LISTEN=0.0.0.0:50000
|
||||
AFV_UDP_ADVERTISE_IPV4=voice.example.com:50000
|
||||
DATABASE_SOURCE_NAME=/db/openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
| Name | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `LOG_DEBUG` | Set to `true` for slog debug logging (FSD + web). Default is info / release. | *(unset)* |
|
||||
| `LOG_DEBUG` | Set to `true` for slog debug logging (FSD + web + AFV). Default is info / release. | *(unset)* |
|
||||
| `GIN_MODE` | Gin mode for the web UI and FSD service HTTP. openfsd defaults to `release` when unset (Gin’s own default is `debug`). | `release` (when unset) |
|
||||
| `GIN_LOGGER` | When set (any non-empty value), enables Gin’s request logger middleware on the web server. | *(unset)* |
|
||||
|
||||
@@ -81,7 +129,9 @@ Default is **off**. See [Deployment](Deployment.md#optional-multi-node-cluster)
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| *(none)* | Both FSD and web |
|
||||
| *(none)* | FSD + web (AFV off) |
|
||||
| `-fsd` | FSD only |
|
||||
| `-web` | Web only |
|
||||
| `-fsd -web` | Both (same as default) |
|
||||
| `-afv` | AFV voice only |
|
||||
| `-fsd -web` | FSD + web (same as default) |
|
||||
| `-fsd -web -afv` | All three services in one process |
|
||||
|
||||
@@ -4,10 +4,11 @@ openfsd is a **single binary** (`openfsd`) that can run:
|
||||
|
||||
1. The FSD server (TCP protocol + internal service HTTP)
|
||||
2. The web UI and `/api/v1`
|
||||
3. Optionally, the **AFV** voice API + UDP voice server (`-afv`)
|
||||
|
||||
By default both run in one process. Use CLI flags to select services.
|
||||
By default **FSD + web** run in one process (AFV stays off). Use CLI flags to select services.
|
||||
|
||||
Storage is **SQLite only** (a file on disk, or `:memory:` for ephemeral use). If you are upgrading from a PostgreSQL deployment, see [Migrating from PostgreSQL](Migrating-from-PostgreSQL.md).
|
||||
Storage is **SQLite only** by default (a file on disk, or `:memory:` for ephemeral use). Optional multi-node uses **rqlite** for durable users/config. If you are upgrading from a PostgreSQL deployment, see [Migrating from PostgreSQL](Migrating-from-PostgreSQL.md).
|
||||
|
||||
## Docker Compose (recommended)
|
||||
|
||||
@@ -32,7 +33,7 @@ Images: **`ghcr.io/renorris/openfsd`** — CI publishes `:latest` from **`main`
|
||||
### Service selection
|
||||
|
||||
```bash
|
||||
# Both (default CMD)
|
||||
# FSD + web (default CMD)
|
||||
docker run --rm -p 6809:6809 -p 8000:8000 ghcr.io/renorris/openfsd:latest
|
||||
|
||||
# FSD only
|
||||
@@ -42,6 +43,14 @@ docker run --rm -p 6809:6809 ghcr.io/renorris/openfsd:latest /openfsd -fsd
|
||||
docker run --rm -p 8000:8000 \
|
||||
-e FSD_HTTP_SERVICE_ADDRESS=http://fsd-host:13618 \
|
||||
ghcr.io/renorris/openfsd:latest /openfsd -web
|
||||
|
||||
# AFV only (set advertise + public URL for real clients)
|
||||
docker run --rm -p 8080:8080 -p 50000:50000/udp \
|
||||
-e AFV_API_LISTEN=0.0.0.0:8080 \
|
||||
-e AFV_UDP_ADVERTISE_IPV4=your.public.host:50000 \
|
||||
-e AFV_API_PUBLIC_BASE_URL=https://voice.example \
|
||||
-e DATABASE_SOURCE_NAME=/path/to/shared.db?... \
|
||||
ghcr.io/renorris/openfsd:latest /openfsd -afv
|
||||
```
|
||||
|
||||
Back up the SQLite volume (or the `.db` file) regularly.
|
||||
@@ -54,12 +63,14 @@ go build -o openfsd ./cmd/openfsd
|
||||
export DATABASE_SOURCE_NAME=./openfsd.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)
|
||||
export DATABASE_AUTO_MIGRATE=true
|
||||
|
||||
./openfsd # both FSD and web
|
||||
./openfsd # FSD + web
|
||||
./openfsd -fsd # FSD only
|
||||
./openfsd -web # web only
|
||||
./openfsd -afv # AFV only
|
||||
./openfsd -fsd -web -afv
|
||||
```
|
||||
|
||||
See [Configuration](Configuration.md) for the full environment list.
|
||||
See [Configuration](Configuration.md) for the full environment list (including `AFV_*`).
|
||||
|
||||
## Windows
|
||||
|
||||
@@ -85,7 +96,9 @@ To reset the database, delete the created `openfsd.db` file next to `run-windows
|
||||
|
||||
- Prefer a single pod/process with a persistent volume for the SQLite file for small/medium networks.
|
||||
- If you split `-fsd` and `-web` across hosts, set `FSD_HTTP_SERVICE_ADDRESS` on the web process to the FSD internal service HTTP address (default `http://127.0.0.1:13618` when colocated).
|
||||
- Multi-FSD web: set `FSD_HTTP_SERVICE_ADDRESSES` (`nodeID=http://host:port,...`) for online_users aggregation and kick routing.
|
||||
- Do not put two writers on the same SQLite file over NFS without understanding SQLite locking limits; local disk or a block volume is preferred.
|
||||
- AFV may run colocated (`-fsd -web -afv`) or as a separate process sharing the same user database DSN.
|
||||
|
||||
## Optional multi-node cluster
|
||||
|
||||
@@ -100,6 +113,7 @@ See also `docs/design/distributed-openfsd.md` and the sample compose file `docke
|
||||
| Durable users/config | **rqlite** (Raft SQLite) | `DATABASE_DRIVER=rqlite`, HTTP DSN |
|
||||
| Live callsigns/positions | In-process postoffice + **TCP mesh** | Never stored in rqlite |
|
||||
| Control plane | Web ×N → service HTTP on each FSD | `FSD_HTTP_SERVICE_ADDRESSES` |
|
||||
| Voice (optional) | AFV process(es) | Separate from FSD mesh; see AFV section |
|
||||
|
||||
### Required env (FSD edges)
|
||||
|
||||
@@ -154,3 +168,20 @@ Default `docker compose up` remains **one container + SQLite**.
|
||||
### CI note
|
||||
|
||||
Optional multi-node compose is **manual / ops** (`docker compose -f docker-compose.cluster.yml`). Default CI remains single-node `go test` + MemoryMesh unit/e2e tests (no required cluster compose job in v1).
|
||||
|
||||
## Optional AFV voice
|
||||
|
||||
AFV is **opt-in** (`-afv`). It is not started by the default Docker CMD.
|
||||
|
||||
| Plane | Port (defaults) | Notes |
|
||||
|-------|-----------------|--------|
|
||||
| AFV REST | `AFV_API_LISTEN` default `127.0.0.1:8080` | Auth, callsign, transceivers; prefer TLS in production |
|
||||
| AFV UDP | `AFV_UDP_LISTEN` default `0.0.0.0:50000` | CryptoDTO Opus relay (opaque; no decode) |
|
||||
|
||||
**Required for clients to connect UDP:** `AFV_UDP_ADVERTISE_IPV4` (and optionally `AFV_UDP_ADVERTISE_IPV6`) — the address embedded in channel config for clients. Behind NAT, this must be the **public** host:port.
|
||||
|
||||
Share the same `DATABASE_*` as FSD so AFV authenticates against the same certificates. Optional `AFV_REQUIRE_FSD_ONLINE=true` gates voice sessions on the FSD online_users service.
|
||||
|
||||
Multi-node AFV mesh (`AFV_CLUSTER_*`) directory + AT relay is implemented for in-process/MemoryMesh testing; **production TCP mesh is not enabled** in the binary yet (`AFV_CLUSTER_ENABLED=true` fails closed until PR-10b). See `docs/design/afv-server.md` and `docs/design/afv-mesh-pr10.md`.
|
||||
|
||||
Full env list: [Configuration](Configuration.md#afv-voice-optional).
|
||||
|
||||
30
wiki/Home.md
30
wiki/Home.md
@@ -8,20 +8,36 @@ Operator documentation for [openfsd](https://github.com/renorris/openfsd).
|
||||
|
||||
| Page | Description |
|
||||
|------|-------------|
|
||||
| [Deployment](Deployment.md) | Docker Compose, Windows, and single-binary run |
|
||||
| [Configuration](Configuration.md) | Env vars and persistent DB settings |
|
||||
| [Client Connection](Client-Connection.md) | VRC, Euroscope, Swift, vPilot, xPilot |
|
||||
| [Deployment](Deployment.md) | Docker Compose, Windows, single-binary run, optional cluster + AFV |
|
||||
| [Configuration](Configuration.md) | Env vars (FSD, web, cluster, AFV) and persistent DB settings |
|
||||
| [Client Connection](Client-Connection.md) | VRC, Euroscope, Swift, vPilot, xPilot; optional AFV voice |
|
||||
| [Migrating from PostgreSQL](Migrating-from-PostgreSQL.md) | Convert an existing Postgres DB to SQLite |
|
||||
|
||||
## Admin web tools (in the product UI)
|
||||
## Admin / instructor web tools (in the product UI)
|
||||
|
||||
| URL | Who | Notes |
|
||||
|-----|-----|--------|
|
||||
| `/sweatbox` | Administrator | Live ground/taxi simulator control (needs FSD + sweatbox enabled) |
|
||||
| `/sweatbox/manual` | Administrator | Instructor user manual (also linked from the control panel, opens in a new tab) |
|
||||
| `/airport-editor` | Administrator | Map-first `.apt` / `.air` authoring; **download only** (no server save). Validate tab has live client checks + optional server confirm. Download files, then load them on `/sweatbox`. Design: repo `docs/design/apt-air-editor.md` |
|
||||
| `/dashboard` | Any signed-in user (OBS+) | Connection summary (HTML); optional Leaflet map PE |
|
||||
| `/account` | Any signed-in user | Change password; soft-delete account (optional hard-delete via env) |
|
||||
| `/usereditor` | Supervisor+ | Certificate directory create/update |
|
||||
| `/configeditor` | Administrator | Server config, API tokens, JWT secret reset |
|
||||
| `/sweatbox` | Instructor1+ | Live ground/taxi simulator control (needs FSD + sweatbox enabled) |
|
||||
| `/sweatbox/manual` | Instructor1+ | Instructor user manual (also linked from the control panel) |
|
||||
| `/airport-editor` | Instructor1+ | Map-first `.apt` / `.air` authoring; **download only** (no server save). Validate tab has live client checks + optional server confirm. Download files, then load them on `/sweatbox`. Design: repo `docs/design/apt-air-editor.md` |
|
||||
|
||||
## Operator REST
|
||||
|
||||
JSON under `/api/v1` (Bearer API token or session cookie + CSRF). Version pin via `OpenFSD-API-Version`. Full guide and OpenAPI: repository `internal/web/README.md` and `GET /api/v1/openapi.yaml`.
|
||||
|
||||
## Optional subsystems
|
||||
|
||||
| Feature | Flag / env | Docs |
|
||||
|---------|------------|------|
|
||||
| AFV voice | `-afv` + `AFV_*` | [Configuration](Configuration.md#afv-voice-optional), `docs/design/afv-server.md` |
|
||||
| Multi-node FSD | `CLUSTER_ENABLED` + rqlite | [Deployment](Deployment.md#optional-multi-node-cluster), `docs/design/distributed-openfsd.md` |
|
||||
|
||||
## Quick links
|
||||
|
||||
- Images: `ghcr.io/renorris/openfsd` (`:latest` = main, `:dev` = unstable tip, `sha-*`)
|
||||
- Protocol notes: repository `docs/`
|
||||
- Package / import rules for contributors: repository `AGENTS.md`
|
||||
|
||||
@@ -14,7 +14,7 @@ go build -o openfsd-migrate-to-sqlite ./cmd/openfsd-migrate-to-sqlite
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Stop** openfsd (FSD + web) so nothing writes to either database during the copy.
|
||||
1. **Stop** openfsd (FSD, web, and AFV if running) so nothing writes to either database during the copy.
|
||||
2. **Back up** PostgreSQL (`pg_dump`) and keep a copy until you confirm the new server works.
|
||||
3. **Run the migrator**:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user