尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Open-source indoor cycling with friends — and bots, so the road is never empty.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Hook up a smart trainer, open the browser and ride a virtual route together with others. Every server is just a Docker container: run it at home for your LAN, on a VPS for your club, or join a public one. This repository contains the proof of concept: a Rust game server and a browser client (installable as an app) with a 3D and a 2D view.
Status: proof of concept. Expect breaking changes.
Requirements: Docker with Compose v2 and make. Rust and Node are optional.
make up
Then open http://localhost:8080 in Chrome or Edge, enter a name, connect your
trainer (or pick Simulated trainer) and join. The first make up copies
.env.example to .env; adjust it and run make up again to apply changes.
| Control | Action |
|---|---|
← / → or A / D | change lane |
↑ / ↓ or W / S | simulated trainer power ±10 W |
E / Q or + / − | shift up / down (virtual shifting) |
J | join a race during its countdown / leave it |
L | cycle the HUD layout: auto, cockpit, broadcast, training, race |
M / Esc | open the menu (overview, view, race, training, settings) / close panels |
V | switch between 3D and 2D view |
C / T | cycle camera (chase, close, drone, TV side) / time of day |
P | take a photo (find them under Menu → Finish ride) |
↑ / ↓ in ERG mode | target ±5 W, or intensity ±5 % during a workout |
On a phone or tablet the same controls are on screen: lane arrows at the sides and, with the simulated trainer, a power stepper.
No trainer? Phone paddles. Pick 📱 Phone paddles on the start screen and tap the left and right paddles in turn, like pedal strokes. A steady rhythm in the green band holds about 75 % of your FTP; tapping faster sprints up to 150 %, but drains your energy bar, and it refills when you ease off. Tapping the same side twice is a stumble. Swipe sideways above the paddles to change lanes. Paddle riders race with everyone and get a 📱 by their name. Paddle rides stay in OpenRadlr: no Strava upload and no ride files (photos and the summary card can be saved). Simulated-trainer rides can be downloaded but not uploaded to Strava.
.zwo import),
low-poly 3D view with chase camera, a 2D lane view, an elevation profile, a
minimap, nearby riders, race panel and chat; live player counts in the start
menu and during the ride; ride photos and a summary image; ride summary with
.tcx/.gpx/.fit download and Strava upload (as a virtual ride with its climbing), placed on the map in a virtual world off Mallorca;
power-only trainers and power meters (no resistance control) are supported.Web Bluetooth works in Chromium browsers (Chrome, Edge) on Windows, macOS, Android and ChromeOS — not in Firefox or Safari/iOS. Without Bluetooth you can still ride with the simulated trainer.
Run make to list everything. The most important ones:
| Command | What it does |
|---|---|
make up | Build and start in the background, wait until healthy |
make down | Stop and remove the containers |
make logs | Follow the logs |
make update | git pull, rebuild, restart, wait until healthy |
make test | All tests (Rust and JavaScript) |
make lint | rustfmt, clippy (-D warnings), ESLint |
make check | Everything CI runs |
make dev | Run the server with local cargo, no Docker |
make lock | Create server/Cargo.lock — run once and commit it |
If cargo or npm is not installed, the test and lint targets run them in Docker.
Full guide, including your own reverse proxy and Strava: docs/deploy.md.
git clone <your-repo-url> openradlr && cd openradlr
make setup # creates .env (later: adds new settings to it)
$EDITOR .env # set OPENRADLR_SERVER_NAME etc.
make up
Later, to deploy new commits: make update.
The server exposes aggregate Prometheus metrics at /internal/metrics for
connected riders, configured rider capacity and community distance totals. The
endpoint contains no rider names, IDs, credentials or request details. The
Monitoring stack scrapes it over the private openradlr-metrics Docker
network; its Grafana dashboard also probes the public /healthz URL.
HTTPS: browsers only allow Web Bluetooth on https:// pages or on
http://localhost. For a public server, point a DNS record at the machine and
set in .env:
COMPOSE_PROFILES=tls
OPENRADLR_DOMAIN=ride.example.org
make up then also starts Caddy, which obtains a Let's Encrypt certificate and
proxies to the game server. Ports 80 and 443 must be reachable.
Landing page (optional): the official server runs a static page on the
main domain and the app on a subdomain (openradlr.com and app.openradlr.com).
Add landing to COMPOSE_PROFILES and set OPENRADLR_LANDING_DOMAIN; see
docs/deploy.md. Without it nothing changes.
LAN parties: set OPENRADLR_HTTP_BIND=0.0.0.0. Other machines reach the page
via http://<your-ip>:8080, but their browsers block Bluetooth on plain HTTP.
Use the TLS profile with a real domain, or for quick tests enable
chrome://flags/#unsafely-treat-insecure-origin-as-secure for that address.
All settings live in .env; see .env.example for descriptions.
| Variable | Default | Meaning |
|---|---|---|
OPENRADLR_SERVER_NAME | OpenRadlr | Name shown to riders |
OPENRADLR_ROUTE | rolling-hills | Default route in routes/ (without .json); all routes there can be picked |
OPENRADLR_TARGET_RIDERS | 12 | Humans + bots kept on the road (per route) |
OPENRADLR_MAX_PLAYERS | 50 | Maximum human riders on trainers at the same time, all routes (phone riders not counted) |
OPENRADLR_MAX_PADDLE_PLAYERS | 20 | Maximum riders on phone paddles at the same time, on top of the above |
OPENRADLR_MAX_VISIBLE_RIDERS | 200 | Most riders in one state message; humans first, then the nearest bots |
OPENRADLR_PUBLIC_URL | – | Public address, e.g. https://ride.example.org (Strava, allowed origin) |
OPENRADLR_TRUST_PROXY | false | Client IPs from X-Forwarded-For behind a reverse proxy |
OPENRADLR_MAX_CONNECTIONS_PER_IP | 4 | Game connections per IP |
OPENRADLR_MESSAGES_PER_SECOND | 20 | Message rate limit per connection |
OPENRADLR_STRAVA_CLIENT_ID / _SECRET | – | Enable "Connect with Strava" |
OPENRADLR_REQUIRE_STRAVA | false | Riders must connect Strava to join |
OPENRADLR_ALLOW_SIMULATED_TRAINER | true | Offer the simulated trainer (set false in production) |
OPENRADLR_ALLOW_PADDLE_MODE | true | Offer phone paddles (tap left/right for power; no Strava, no ride files) |
OPENRADLR_PRIVACY_URL | – |
.
├── server/ Rust workspace
│ ├── crates/sim/ simulation core: route, physics, drafting, bots, races (no dependencies)
│ └── crates/server/ network adapter: config, protocol, game loop, HTTP/WebSocket
├── client/
│ ├── public/ browser client (plain ES modules, no build step)
│ └── test/ client unit tests (node:test)
├── routes/ route files (JSON), shared by server and clients
├── landing/ landing page for the main domain (Astro, static)
├── deploy/Caddyfile HTTPS reverse proxy
├── docs/ architecture, protocol, devices, decisions
├── Dockerfile, docker-compose.yml, Makefile, .env.example
└── .github/workflows/ci.yml
OpenRadlr is an independent project. It is not affiliated with, endorsed by or sponsored by Zwift, Wahoo, Strava, Van Rysel or any other company named here; their names only say which products it works with, and they belong to their owners.
| Link to your privacy policy in the start menu |
OPENRADLR_ANALYTICS_DOMAIN / _SCRIPT | – | Opt-in Plausible analytics for the client: your site name, and the script URL if you self-host Plausible (off when the domain is empty) |
OPENRADLR_STUDIO | true | Host the spin studio (group classes) next to the routes |
OPENRADLR_EVENTS | default | Scheduled events: default, off or the path of an events file (events) |
OPENRADLR_EVENT_LOBBY_S | 600 | Seconds an event's lobby opens before the start |
OPENRADLR_TICK_HZ / OPENRADLR_BROADCAST_HZ | 20 / 4 | Simulation and state rate |
OPENRADLR_RACE_COUNTDOWN_S | 30 | Race countdown (time to join) in seconds |
OPENRADLR_MAX_RACE_LAPS | 20 | Upper limit for race laps |
OPENRADLR_BOT_SEED | random | Fixed seed for reproducible bots |
OPENRADLR_AUTH_MODE | open | Only open (local profiles) in the POC |
OPENRADLR_HTTP_BIND / OPENRADLR_HTTP_PORT | 127.0.0.1 / 8080 | Where the web client is published |
COMPOSE_PROFILES, OPENRADLR_DOMAIN | – | Enable HTTPS via Caddy (tls), the landing page (landing) |
OPENRADLR_LANDING_DOMAIN / _URL | – | Domain(s) and address of the landing page |
RUST_LOG | info | Log level |