From 227f20b91225a28340329456fc6d2ced8c1ca956 Mon Sep 17 00:00:00 2001 From: Reese Norris Date: Tue, 28 Jul 2026 18:00:34 -0400 Subject: [PATCH] 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. --- README.md | 110 +++++++++++++++++------- docs/design/afv-mesh-pr10.md | 11 ++- docs/design/afv-server.md | 15 +++- docs/design/airport-editor-undo-redo.md | 2 +- docs/design/apt-air-editor.md | 28 +++--- docs/design/distributed-openfsd.md | 18 +++- docs/design/rest-api-versioning.md | 2 +- docs/index.md | 10 +++ wiki/Client-Connection.md | 13 +++ wiki/Configuration.md | 60 +++++++++++-- wiki/Deployment.md | 41 +++++++-- wiki/Home.md | 30 +++++-- wiki/Migrating-from-PostgreSQL.md | 2 +- 13 files changed, 272 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index e06e35f..bb5dffb 100644 --- a/README.md +++ b/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). diff --git a/docs/design/afv-mesh-pr10.md b/docs/design/afv-mesh-pr10.md index 4dfe607..a5508f7 100644 --- a/docs/design/afv-mesh-pr10.md +++ b/docs/design/afv-mesh-pr10.md @@ -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. diff --git a/docs/design/afv-server.md b/docs/design/afv-server.md index 21d9fbd..ca3d57a 100644 --- a/docs/design/afv-server.md +++ b/docs/design/afv-server.md @@ -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: diff --git a/docs/design/airport-editor-undo-redo.md b/docs/design/airport-editor-undo-redo.md index fc2bd24..5131361 100644 --- a/docs/design/airport-editor-undo-redo.md +++ b/docs/design/airport-editor-undo-redo.md @@ -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. diff --git a/docs/design/apt-air-editor.md b/docs/design/apt-air-editor.md index 3cd14ba..2a5fdb3 100644 --- a/docs/design/apt-air-editor.md +++ b/docs/design/apt-air-editor.md @@ -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 | diff --git a/docs/design/distributed-openfsd.md b/docs/design/distributed-openfsd.md index e8194ca..62e6743 100644 --- a/docs/design/distributed-openfsd.md +++ b/docs/design/distributed-openfsd.md @@ -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**: diff --git a/docs/design/rest-api-versioning.md b/docs/design/rest-api-versioning.md index fff15b9..ed79b9f 100644 --- a/docs/design/rest-api-versioning.md +++ b/docs/design/rest-api-versioning.md @@ -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/*` | diff --git a/docs/index.md b/docs/index.md index b0b7439..94d6373 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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.* diff --git a/wiki/Client-Connection.md b/wiki/Client-Connection.md index 238568d..240cf7d 100644 --- a/wiki/Client-Connection.md +++ b/wiki/Client-Connection.md @@ -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. diff --git a/wiki/Configuration.md b/wiki/Configuration.md index 4e42191..16f9ef6 100644 --- a/wiki/Configuration.md +++ b/wiki/Configuration.md @@ -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 | diff --git a/wiki/Deployment.md b/wiki/Deployment.md index e42d3e1..9c27784 100644 --- a/wiki/Deployment.md +++ b/wiki/Deployment.md @@ -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). diff --git a/wiki/Home.md b/wiki/Home.md index c490800..0493196 100644 --- a/wiki/Home.md +++ b/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` diff --git a/wiki/Migrating-from-PostgreSQL.md b/wiki/Migrating-from-PostgreSQL.md index 16e195a..8600c6d 100644 --- a/wiki/Migrating-from-PostgreSQL.md +++ b/wiki/Migrating-from-PostgreSQL.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**: