A proxy that sets the username header twice, as when Authentik's nginx snippet and the documented lines are both present, made Node join the values into "brad, brad" and create an account of that name instead of signing in brad. Read each occurrence separately: identical repeats count once, and conflicting ones are refused. The docs now note Authentik's snippet already sets the headers.
300 lines
14 KiB
Markdown
300 lines
14 KiB
Markdown
# Environment Variables
|
|
|
|
Configuration reference for all Flashpoint Web services.
|
|
|
|
## Backend Variables
|
|
|
|
Location: `backend/.env` (local development)
|
|
|
|
**Server:**
|
|
|
|
| Variable | Default | Description |
|
|
| ---------- | ----------- | ------------------------------------------ |
|
|
| `NODE_ENV` | development | Environment: development, production, test |
|
|
|
|
> **Note:** Port (3100) and bind address (0.0.0.0) are hardcoded in the backend.
|
|
> In Docker, use `API_PORT` to map a different host port to the container.
|
|
|
|
**Paths:**
|
|
|
|
| Variable | Default | Description |
|
|
| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
| `FLASHPOINT_PATH` | `D:/Flashpoint` (dev), `/data/flashpoint` (prod) | Root Flashpoint directory. All other paths auto-derived. Edition (Infinity/Ultimate) auto-detected from `version.txt`. |
|
|
| `FLASHPOINT_GAMES_PATH`| `<FLASHPOINT_PATH>/Data/Games` | Override game ZIP directory (rarely needed). |
|
|
| `APP_VERSION` | set by the Docker build | Release version the server reports on Settings → Update. Baked into the official image from the release tag; set it by hand for a non-Docker deployment, or update checks stay off. |
|
|
|
|
**Authentication & Security:**
|
|
|
|
| Variable | Default | Description |
|
|
| -------------------- | --------------------- | ---------------------------------------------------- |
|
|
| `JWT_SECRET` | auto-generated (dev) | Secret for JWT signing (**required in production**) |
|
|
| `JWT_EXPIRES_IN` | 1h | Access token expiration (15m, 1h, 7d, etc.) |
|
|
| `BCRYPT_SALT_ROUNDS` | 10 | Password hash cost (higher = more secure but slower) |
|
|
| `DOMAIN` | http://localhost:5173 | Allowed CORS origin(s), comma-separated (see below) |
|
|
| `COOKIE_SECURE` | auto | Secure flag on auth cookies: `auto`, `true`, `false` |
|
|
| `UV_THREADPOOL_SIZE` | 16 | Concurrent file I/O threads (Node's own default is 4) |
|
|
| `DB_WORKER_COUNT` | 4 | Read-only SQLite query workers (1-16) |
|
|
|
|
Generate secure JWT secret:
|
|
|
|
```bash
|
|
openssl rand -hex 64
|
|
```
|
|
|
|
**COOKIE_SECURE — auth cookies over HTTP and HTTPS:**
|
|
|
|
Browsers discard a cookie marked `Secure` when it arrives over plain HTTP, which
|
|
logs users straight back out. `auto` therefore decides per request: cookies are
|
|
marked `Secure` only on connections that actually used TLS, so one deployment can
|
|
serve a LAN over HTTP and the internet over HTTPS at the same time.
|
|
|
|
Detection reads `X-Forwarded-Proto`, which is trusted only from a proxy. Set
|
|
`COOKIE_SECURE=true` if your TLS-terminating proxy does not send that header, and
|
|
`false` only if you deliberately want the flag off everywhere.
|
|
|
|
**DOMAIN — allowed origins:**
|
|
|
|
`DOMAIN` accepts one origin or a comma-separated list, so a single deployment can
|
|
be reached over several addresses:
|
|
|
|
```bash
|
|
DOMAIN=http://192.168.0.118,https://flashpoint.example.com
|
|
```
|
|
|
|
The first entry is the primary origin, used where a single value is required.
|
|
Requests from any listed origin are accepted, as are requests from hostnames in
|
|
the domains table.
|
|
|
|
Two notes that save debugging time:
|
|
|
|
- Same-origin requests are always allowed, so `SERVE_FRONTEND=true` deployments
|
|
work at any address without listing it here. You only need `DOMAIN` when the UI
|
|
is served from a different origin than the API, or behind a reverse proxy that
|
|
rewrites the `Host` header.
|
|
- Default ports are normalized away: `http://host:80` and `http://host` are
|
|
treated as the same origin, as are `https://host:443` and `https://host`.
|
|
Non-default ports must be written out, for example `http://host:8080`.
|
|
|
|
**Rate Limiting:**
|
|
|
|
| Variable | Default | Description |
|
|
| ------------------------- | ------- | ----------------------- |
|
|
| `RATE_LIMIT_WINDOW_MS` | 60000 | Time window (ms) |
|
|
| `RATE_LIMIT_MAX_REQUESTS` | 100 | Max requests per window |
|
|
|
|
**Logging:**
|
|
|
|
| Variable | Default | Description |
|
|
| ----------- | ------------------------------ | -------------------------------------------- |
|
|
| `LOG_LEVEL` | info | Level: error, warn, info, debug |
|
|
| `LOG_FILE` | `/app/logs/backend.log` (prod) | Log file path (unset in dev = stdout only) |
|
|
|
|
**Database Performance:**
|
|
|
|
| Variable | Default | Description |
|
|
| --------------------- | ----------- | ---------------------------------------------------------------- |
|
|
| `ENABLE_LOCAL_DB_COPY`| false | Copy flashpoint.sqlite locally (for network storage SMB/NFS) |
|
|
| `SQLITE_CACHE_SIZE` | -64000 | SQLite page cache size (negative = KB, -64000 = 64MB) |
|
|
| `SQLITE_MMAP_SIZE` | 268435456 | Memory-mapped I/O size in bytes (256MB) |
|
|
| `ENABLE_CACHE_PREWARM`| true | Pre-load common queries on startup |
|
|
|
|
**Game Content Serving:**
|
|
|
|
| Variable | Default | Description |
|
|
| -------------------- | ------- | ------------------------------------------------ |
|
|
| `ENABLE_CGI` | false | Enable PHP CGI execution for legacy game content |
|
|
| `HOME_RECENT_HOURS` | 24 | Hours to look back for "recently added" games |
|
|
|
|
**Frontend Serving:**
|
|
|
|
The backend serves the built React UI in the single-image deployment, so the app
|
|
and API share one origin.
|
|
|
|
| Variable | Default | Description |
|
|
| -------------------- | ----------------------------- | -------------------------------------------------------------- |
|
|
| `SERVE_FRONTEND` | true in production, else false | Serve the built frontend and SPA fallback from the backend |
|
|
| `FRONTEND_DIST_PATH` | `<backend>/../frontend/dist` | Location of the built frontend (rarely needed) |
|
|
| `RUFFLE_DATA_PATH` | `/app/data/ruffle` in production | Where Ruffle is installed. Must sit on a mounted volume, or an in-app update is lost when the container is recreated |
|
|
|
|
**Streaming:**
|
|
|
|
Games that can't run in a browser, such as Java applets, can be streamed from a
|
|
server-side container by a separate session manager (flashpoint-ultimate-server).
|
|
The backend asks it which games are streamable and starts sessions for players.
|
|
Browsers reach each stream at `/stream/<id>/` on this site, so the reverse proxy
|
|
in front of flashpoint-web must route `/stream/` to the session manager,
|
|
including WebSocket upgrades. Saves for streamed games are kept in each
|
|
browser's IndexedDB.
|
|
|
|
| Variable | Default | Description |
|
|
| --------------------- | ------- | ---------------------------------------------------------------------- |
|
|
| `STREAMING_API_URL` | - | Session manager's internal API, e.g. `http://flashpoint-session-manager:8080` |
|
|
| `STREAMING_API_TOKEN` | - | Token the session manager expects. Streaming is off unless both are set |
|
|
|
|
**Trusted Header Login:**
|
|
|
|
When a reverse proxy such as Authentik (forward auth) authenticates users in
|
|
front of flashpoint-web, it can pass the signed-in username in a header so
|
|
users don't log in twice. The backend believes the header only on connections
|
|
from `TRUSTED_PROXIES`, checked against the connection's own address, not
|
|
`X-Forwarded-For`. The proxy must always set the header, overwriting anything a
|
|
client sends, and flashpoint-web must not be reachable from untrusted networks
|
|
except through it.
|
|
|
|
Users are matched by username, so an existing account of that name is signed
|
|
in; otherwise an account is created (the first one as admin, as with
|
|
registration). New accounts get a random password. While signed in through the
|
|
proxy, users can set a password from Settings without knowing the current one,
|
|
then log in with it when reaching flashpoint-web without the proxy, such as on a
|
|
LAN.
|
|
|
|
| Variable | Default | Description |
|
|
| ------------------------- | ------- | ------------------------------------------------------------------------ |
|
|
| `TRUSTED_HEADER_USERNAME` | - | Header holding the username, e.g. `X-authentik-username` |
|
|
| `TRUSTED_HEADER_EMAIL` | - | Optional header holding the email used for new accounts |
|
|
| `TRUSTED_PROXIES` | - | Comma-separated proxy addresses or CIDR ranges allowed to set the header |
|
|
|
|
With nginx and Authentik's forward auth, each protected `location` needs these
|
|
lines once. Authentik's generated nginx snippet already includes them; adding
|
|
them again sends each header twice:
|
|
|
|
```nginx
|
|
auth_request_set $authentik_username $upstream_http_x_authentik_username;
|
|
auth_request_set $authentik_email $upstream_http_x_authentik_email;
|
|
proxy_set_header X-authentik-username $authentik_username;
|
|
proxy_set_header X-authentik-email $authentik_email;
|
|
```
|
|
|
|
**OpenTelemetry:**
|
|
|
|
| Variable | Default | Description |
|
|
| ------------------------------ | -------------------------- | ---------------------------------------- |
|
|
| `OTEL_ENABLED` | false | Enable distributed tracing and metrics |
|
|
| `OTEL_EXPORTER_OTLP_ENDPOINT` | http://localhost:4318 | OTLP collector endpoint |
|
|
| `OTEL_API_KEY` | - | API key for authentication |
|
|
| `OTEL_SERVICE_NAME` | flashpoint-web | Service name in traces/metrics |
|
|
| `OTEL_TRACES_ENABLED` | true | Enable trace export (when OTEL enabled) |
|
|
| `OTEL_METRICS_ENABLED` | true | Enable metrics export (when OTEL enabled)|
|
|
| `OTEL_METRICS_EXPORT_INTERVAL`| 60000 | Metrics export interval (ms) |
|
|
| `OTEL_LOG_LEVEL` | info | OTEL SDK log level |
|
|
|
|
## Frontend Variables
|
|
|
|
The frontend has no environment variables of its own. API calls use relative
|
|
paths (`/api`), so they resolve against whatever origin serves the app.
|
|
|
|
- **Development**: Vite dev server on port 5173 proxies `/api` to `http://localhost:3100`
|
|
- **Production**: The backend serves the built UI and the API from the same origin
|
|
|
|
## Docker Environment Variables
|
|
|
|
Location: `.env` in project root (used by docker-compose)
|
|
|
|
**Required:**
|
|
|
|
| Variable | Description |
|
|
| ---------------------- | --------------------------------------------------------- |
|
|
| `FLASHPOINT_HOST_PATH` | Host path to Flashpoint installation (bind-mounted as ro) |
|
|
| `JWT_SECRET` | Secret for JWT signing (generate with `openssl rand -hex 64`) |
|
|
|
|
**Port Mapping:**
|
|
|
|
| Variable | Default | Description |
|
|
| ---------- | ------- | ------------------------------------------------------ |
|
|
| `WEB_PORT` | 80 | Host port mapped to the container (serves UI + API) |
|
|
|
|
**Container Settings:**
|
|
|
|
| Variable | Default | Description |
|
|
| ----------- | ------- | ---------------------------------------------------- |
|
|
| `PUID` | 1000 | Container user ID (match host user: `id -u`) |
|
|
| `PGID` | 1000 | Container group ID (match host user: `id -g`) |
|
|
| `TZ` | UTC | Container timezone |
|
|
| `IMAGE_TAG` | latest | Docker image tag (production compose only) |
|
|
| `DATA_PATH` | ./data | Host path for persistent data (production compose) |
|
|
| `LOGS_PATH` | ./logs | Host path for log files |
|
|
|
|
All backend variables listed above are also passable via docker-compose. See
|
|
`docker-compose.yml` for the full list with defaults.
|
|
|
|
## Docker Compose Setup
|
|
|
|
**Create .env file:**
|
|
|
|
```bash
|
|
cat > .env << EOF
|
|
FLASHPOINT_HOST_PATH=/path/to/flashpoint
|
|
JWT_SECRET=$(openssl rand -hex 64)
|
|
DOMAIN=https://flashpoint.example.com
|
|
WEB_PORT=80
|
|
LOG_LEVEL=warn
|
|
EOF
|
|
```
|
|
|
|
**Start services:**
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
## Environment Templates
|
|
|
|
**Development (`backend/.env`):**
|
|
|
|
```bash
|
|
NODE_ENV=development
|
|
FLASHPOINT_PATH=D:/Flashpoint
|
|
JWT_SECRET=development-secret-change-in-production
|
|
DOMAIN=http://localhost:5173
|
|
RATE_LIMIT_MAX_REQUESTS=1000
|
|
LOG_LEVEL=debug
|
|
```
|
|
|
|
**Production (`.env` for Docker):**
|
|
|
|
```bash
|
|
FLASHPOINT_HOST_PATH=/data/flashpoint
|
|
JWT_SECRET=CHANGE-THIS-TO-A-RANDOM-64-CHARACTER-STRING
|
|
DOMAIN=https://flashpoint.example.com
|
|
WEB_PORT=80
|
|
LOG_LEVEL=warn
|
|
```
|
|
|
|
## Validation and Defaults
|
|
|
|
**Required Variables:**
|
|
|
|
**Backend (local dev):**
|
|
|
|
- `FLASHPOINT_PATH` (only this is required; others auto-derive)
|
|
|
|
**Docker Production:**
|
|
|
|
- `FLASHPOINT_HOST_PATH` (must point to Flashpoint installation)
|
|
- `JWT_SECRET` (must be changed from default)
|
|
|
|
**Verify configuration:**
|
|
|
|
```bash
|
|
cd backend
|
|
node -e "require('dotenv').config(); console.log(process.env.FLASHPOINT_PATH)"
|
|
```
|
|
|
|
## Security Best Practices
|
|
|
|
1. Never commit .env files to version control
|
|
2. Use strong JWT secrets (64+ characters) in production
|
|
3. Restrict CORS origins to your domain only (no wildcards)
|
|
4. Use HTTPS in production (update DOMAIN to https://)
|
|
5. Limit exposed ports (don't expose all services)
|
|
6. Rotate secrets periodically
|
|
7. Use environment-specific files (.env.production, .env.development)
|
|
8. Store secrets in secure management system (AWS Secrets Manager, HashiCorp
|
|
Vault)
|
|
9. Use read-only mounts for Flashpoint data in Docker
|
|
|
|
## Next Steps
|
|
|
|
- [Docker Deployment](./docker-deployment.md)
|
|
- [Security Considerations](./security-considerations.md)
|