Skip to content

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 VariableDefaultDescription
SHILLING_AUTH_MODEnonenone 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_URLSstun:stun.l.google.com:19302Comma-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_TTL86400TURN 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/config
  • GET /api/household
  • GET /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:

VariableValue
OTEL_SERVICE_NAMEshilling-signaling
OTEL_EXPORTER_OTLP_ENDPOINThttps://ingest.maple.dev (US) or https://ingest.eu.maple.dev (EU)
OTEL_EXPORTER_OTLP_PROTOCOLhttp/protobuf
OTEL_EXPORTER_OTLP_HEADERSAuthorization=Bearer <ingest key>
OTEL_LOGS_EXPORTERnone when container logs are collected separately
OTEL_RESOURCE_ATTRIBUTESdeployment.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.