Standalone Docker service that connects Prusa Core One printers (via PrusaLink) to Obico (self-hosted or cloud). Runs as an Obico agent — no modifications to Obico or PrusaLink required. One container = one printer.
- Setup wizard: PrusaLink credentials → camera test → Obico pairing
- Live status forwarding (temperatures, job progress, state)
- MJPEG snapshot upload for AI failure detection
- WebRTC live stream via Janus in the Obico control panel
- Pause / resume / cancel from Obico
The bridge acts as a translator between your Prusa Core One printer and Obico. It polls the printer's PrusaLink HTTP API for status and job data, captures the RTSP camera stream via ffmpeg, and forwards everything to Obico over WebSocket. The Obico control panel then shows your printer's state, lets you watch the live stream, and can pause or cancel a print if the AI detects a failure.
- Docker (20.10+) with Docker Compose v2
- A Prusa Core One with PrusaLink enabled and reachable on your LAN
- PrusaLink credentials (username + password from the printer's network settings)
- RTSP camera stream from the printer (
rtsp://[printer-ip]/live) — mandatory, Obico requires a live stream for AI failure detection - An Obico account (self-hosted or https://app.obico.io)
- The Docker host's LAN IP (needed for WebRTC — see
JANUS_HOST_IPbelow)
Pull the image and run it with your Docker host's LAN IP:
docker run -d \
-p 3000:3000 \
-p 10100-10200:10100-10200/udp \
-v ./config:/config \
-e JANUS_HOST_IP=192.168.1.x \
rleban/sentry-bridge:latest# Open http://localhost:3000 → follow setup wizardReplace 192.168.1.x with the LAN IP of the machine running Docker. Without this, the WebRTC live stream will not work.
For the full guide → docs/guide/
For persistent setups, use the included docker-compose.yml at the repo root, or copy this block:
services:
bridge:
image: rleban/sentry-bridge:latest
container_name: sentry-bridge
ports:
- "3000:3000"
- "10100-10200:10100-10200/udp" # WebRTC ICE media (browser ↔ Janus)
volumes:
- ./config:/config
environment:
- PORT=3000
- CONFIG_PATH=/config/config.json
- JANUS_MODE=bundled
- JANUS_DEBUG_LEVEL=2
- JANUS_HOST_IP=${JANUS_HOST_IP:-}
- CIRCUIT_BREAKER_THRESHOLD=5
- CIRCUIT_BREAKER_RESET_TIMEOUT_MS=60000
- RETRY_BASE_DELAY_MS=1000
- RETRY_MAX_DELAY_MS=30000
- HEALTHCHECK_CRITICAL_TIMEOUT_MS=120000
healthcheck:
test:
[
"CMD-SHELL",
'node -e "fetch(''http://localhost:3000/api/health/live'').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"',
]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
restart: unless-stopped
stop_grace_period: 30sdocker compose up -d
# Open http://localhost:3000 → follow setup wizardThe repo includes a ready-to-use docker-compose.yml — just clone and run.
| Variable | Default | Required / Optional | Description |
|---|---|---|---|
PORT |
3000 |
Optional | HTTP port |
CONFIG_PATH |
/config/config.json |
Optional | Config file path |
JANUS_HOST_IP |
(unset) | Required (for WebRTC) | LAN IP of the Docker host. Janus advertises this as its ICE candidate so the browser can reach it. Without this, the live stream falls back to MJPEG snapshots. |
JANUS_MODE |
auto |
Optional | bundled — bridge manages Janus binary; hosted — external/sidecar; auto — detect |
JANUS_DEBUG_LEVEL |
2 |
Optional | Janus log verbosity: 0=Fatal 1=Err 2=Warn 3=Info 4=Verbose 5=Huge |
CIRCUIT_BREAKER_THRESHOLD |
5 |
Optional | Consecutive PrusaLink failures before circuit opens |
CIRCUIT_BREAKER_RESET_TIMEOUT_MS |
60000 |
Optional | Time (ms) before retrying after circuit opens |
RETRY_BASE_DELAY_MS |
1000 |
Optional | Initial retry delay (ms) |
RETRY_MAX_DELAY_MS |
30000 |
Optional | Maximum retry delay (ms) |
HEALTHCHECK_CRITICAL_TIMEOUT_MS |
120000 |
Optional | Time (ms) a critical component can stay DOWN before /api/health/ready returns 503 |
The wizard writes /config/config.json on the mounted volume. All fields can also be set manually:
{
"prusalink": {
"url": "http://192.168.1.x",
"username": "maker",
"password": "your-prusalink-password"
},
"camera": {
"rtspUrl": "rtsp://192.168.1.x/live",
"frameIntervalSeconds": 10
},
"obico": {
"serverUrl": "https://app.obico.io",
"apiKey": ""
},
"polling": {
"statusIntervalMs": 5000
}
}prusalink— printer URL, username, and password for HTTP Digest Auth access to PrusaLinkcamera— RTSP stream URL and snapshot interval in secondsobico— server URL and pairing token (apiKeyis empty until the wizard completes pairing)polling— polling interval in milliseconds (default: 5000)
On first start the bridge serves a 4-step wizard at http://localhost:3000:
- PrusaLink — enter printer URL (e.g.
http://192.168.x.x), username (maker), and password. The wizard performs a live connection test before continuing. - Camera — the wizard probes
rtsp://[printer-ip]/liveand shows a preview frame. This step is mandatory — it blocks until a frame is received. - Obico Pairing — enter your Obico server URL (
https://app.obico.ioor your self-hosted instance). The wizard displays a 6-digit pairing code. Confirm it in Obico's "Link Printer" dialog. - Done — redirect to the dashboard. The bridge starts forwarding status and frames.
These are intentional product decisions, not bugs:
- One container = one printer. Simplifies config, isolation, and restarts. Run multiple containers on different ports for multiple printers.
- RTSP camera is mandatory. Obico's AI failure detection requires a live stream — there is no "disable camera" mode.
- Manual printer controls are limited to pause / resume / cancel. The PrusaLink API does not expose file upload, filament load/unload, or print queue management, so the bridge does not either.
- Symptom: Wizard step 1 fails with 401 Unauthorized
- Check URL has no trailing slash:
http://<printer-ip>nothttp://<printer-ip>/ - Verify credentials in PrusaLink: Settings → Network → API Key / User Password
- Test from host:
curl -u <username>:<password> --digest http://<printer-ip>/api/v1/status
- Symptom: Wizard step 2 blocks, no frame preview
- Confirm printer has a camera (Buddy3D board) and RTSP is enabled
- Test from host:
ffmpeg -rtsp_transport tcp -i rtsp://<printer-ip>/live -frames:v 1 -f image2 test.jpg - Check LAN: printer and Docker host on the same subnet, no VLAN isolation
- Symptom: Wizard step 3 shows pairing code but Obico never confirms
- Verify the server URL (
https://app.obico.ioor your self-hosted URL) — no trailing slash - Check firewall between bridge and Obico server (WebSocket on 443/80)
- Container logs:
docker logs <container> | grep obico
- Symptom: Obico control panel loads but video is black / spinning
JANUS_HOST_IPmust be set to the Docker host's LAN IP, not127.0.0.1or0.0.0.0- Confirm UDP ports
10100-10200are published and reachable from the browser - Test:
docker exec <container> env | grep JANUS_HOST_IP - Restart after change:
docker compose up -d --force-recreate
For local development or custom builds:
- Node.js 22+
- ffmpeg
- Docker Desktop (for Janus sidecar on Mac)
git clone https://github.com/reneleban/sentry-bridge.git
cd sentry-bridge
npm run install:allnpm run dev:all # backend (ts-node watch) + frontend (Vite)Frontend proxies /api/* to the backend automatically.
On Mac, Janus cannot be installed natively. Run it as a Docker sidecar:
docker compose -f docker-compose.dev.yml up -dThis starts Janus with:
- WS on port
8188(bridge relay) - RTP input on port
17732/udp(from ffmpeg) - ICE media on ports
10100–10200/udp(WebRTC)
For WebRTC to work during local development, set nat_1_1_mapping in config/janus/janus.jcfg to your Mac's LAN IP (e.g. 192.168.1.x). This tells Janus which IP to advertise for ICE so the browser can reach it.
The bridge auto-detects a running Janus on ws://127.0.0.1:8188 before looking for a local binary.
In production (Docker image), Janus runs natively inside the container — set JANUS_HOST_IP in the environment instead of editing the config file directly.
npm run test:backendnpm run build:all # TypeScript + Vite → dist/ + frontend/dist/
npm run build:docker # Docker image (single platform)Prusa Core One
├── PrusaLink HTTP → Bridge (status poll, pause/resume/cancel)
└── RTSP /live → ffmpeg → H.264 RTP → Janus (WebRTC)
↕ WS relay
Browser ← Obico Server ← /ws/janus/{id}/ ← Bridge
- Config module — reads/writes JSON from mounted volume
- PrusaLink client — HTTP Digest Auth, polls status + job
- Camera module — RTSP → JPEG frames (MJPEG) + H.264 RTP (WebRTC)
- Janus manager — spawns/detects Janus WebRTC gateway
- Janus relay — bidirectional WS relay: local Janus ↔ Obico
- Obico agent — WebSocket to Obico, pairing flow, status + frame forwarding