Server
The Shilling server is a lightweight Ktor application that enables multi-device sync. It provides four services:
- WebSocket signaling for WebRTC peer discovery
- Hosted auth/config bootstrap for Supabase-backed deployments
- Hosted household metadata for server-assigned household lookup
- ICE server configuration with TURN credential generation
The server is optional. When running, it helps devices in the same household find each other, but it does not relay or store application data.
Running the Server
just server
This starts the server on port 8081 in self-hosted mode by default. If a repo-root .env file exists, the recipe exports it before launch and fails fast when hosted Supabase mode is incomplete.
Configuration
| Environment Variable | Default | Description |
|---|---|---|
SHILLING_AUTH_MODE | none | none for self-hosted signaling only, supabase for hosted auth/bootstrap |
SHILLING_SUPABASE_URL | (empty) | Supabase project URL |
SHILLING_SUPABASE_ANON_KEY | (empty) | Supabase publishable key served to clients from /api/config |
SHILLING_SUPABASE_SERVICE_KEY | (empty) | Supabase server key used for hosted user_profiles lookups |
SHILLING_SUPABASE_JWT_SECRET | (empty) | Legacy HS256 secret for old Supabase projects only |
SHILLING_STUN_URLS | stun:stun.l.google.com:19302 | Comma-separated STUN server URLs |
SHILLING_TURN_URLS | (empty) | Comma-separated TURN server URLs |
SHILLING_TURN_SECRET | (empty) | coturn shared secret for credential generation |
SHILLING_TURN_TTL | 86400 | TURN credential validity in seconds (24h) |
Architecture
The server is control-plane only. Hosted metadata lives in Supabase, and application data stays on-device and moves only over WebRTC P2P channels.
Hosted mode currently exposes:
GET /health(lightweight liveness check)GET /api/configGET /api/householdGET /api/ice-servers- WebSocket
/ws/signal
Modern Supabase projects use JWKS-backed access-token verification. SHILLING_SUPABASE_JWT_SECRET is only a compatibility fallback for older HS256 projects.
Observability
The server image includes a pinned OpenTelemetry Java agent. Enable it at runtime with JAVA_TOOL_OPTIONS=-javaagent:/opt/opentelemetry-javaagent.jar; the image also works without tracing when this variable is absent. For hosted Maple, set:
| Variable | Value |
|---|---|
OTEL_SERVICE_NAME | shilling-signaling |
OTEL_EXPORTER_OTLP_ENDPOINT | https://ingest.maple.dev (US) or https://ingest.eu.maple.dev (EU) |
OTEL_EXPORTER_OTLP_PROTOCOL | http/protobuf |
OTEL_EXPORTER_OTLP_HEADERS | Authorization=Bearer <ingest key> |
OTEL_LOGS_EXPORTER | none when container logs are collected separately |
OTEL_RESOURCE_ATTRIBUTES | deployment.environment.name=production or staging |
In Coolify, store the ingest key as a secret shared variable and reference it from the application variable, for example Authorization=Bearer {{team.Maple_API_key}}. Do not commit or print the key. Configure the container health check on GET /health after deploying the image with that route. Monitor https://api.shilling.finance/health from outside the host as well, because the local health check cannot detect a DNS, proxy, or host outage.
Signaling logs intentionally omit household and device IDs, IP addresses, raw signaling frames, SDP, and ICE candidates. Review any new logging before exporting container output.
