Development Environment
Everything runs in containers; the host needs only Docker + VS Code. The
environment is self-verifying: make doctor is the authority on whether
your setup can do what the specs assume.
Quickstart
Section titled “Quickstart”git clone <repo> fjarr && cd fjarrcp .env.example .env # then check RENDER_GID matches your host:stat -c %g /dev/dri/renderD128 # → RENDER_GID in .envcode . # "Reopen in Container"# post-create runs pnpm install, cargo fetch, cmake configure, doctormake doctorOr without VS Code: docker compose up -d dev robot-sim, then
docker compose exec dev bash.
Services (docker-compose.yml)
Section titled “Services (docker-compose.yml)”| Service | Profile | Purpose |
|---|---|---|
dev | default | the toolbox: C++/GStreamer 1.24 + Rust + Node 22 (see the Dockerfile’s commented package groups) |
robot-sim | default | Xvfb fake robot desktop at :99 (openbox + moving apps); watch it at http://localhost:6080 (noVNC) |
fjarr-server | demo, stack | the production sidecar image, built from signaling/ only |
demo-backend | demo | the TS “customer backend” beside the sidecar |
demo-robot | demo | the C++ “customer robot” capturing robot-sim |
demo-dashboard | demo | Vite dev server on http://localhost:5173 |
coturn | turn | TURN relay in use-auth-secret mode (ephemeral creds only) |
make demo-up = docker compose --profile demo up -d — the full customer
topology. The dev and robot-sim containers share /tmp/.X11-unix via the
x11sock volume, which is why DISPLAY=:99 works inside dev.
The doctor {#doctor}
Section titled “The doctor {#doctor}”make doctor → .devcontainer/doctor.sh. Checks, and what a failure means:
| Check | On failure |
|---|---|
| toolchains (cmake/cargo/node/…) | image build incomplete — rebuild dev |
GStreamer elements (webrtcbin, vah264enc, ximagesrc, …) | missing plugin package — see Dockerfile groups |
x264enc absent | if present: GPL plugin leaked in — ADR-0011 violation, fix the image |
/dev/dri accessible | RENDER_GID in .env ≠ host render group; fix + docker compose build dev |
vainfo encode entrypoints + vah264enc smoke pipeline | iHD driver problem — see GPU troubleshooting |
DISPLAY=:99 + ximagesrc capture | robot-sim not up, or stale X socket — docker compose restart robot-sim |
libei/pipewire/libevdev pkg-config | dev packages missing from image |
/dev/uinput | WARN by default; opt in via the uinput override |
webrtcsink | WARN until the ADR-0007 spike layer exists |
Opt-in overrides (conscious privilege grants)
Section titled “Opt-in overrides (conscious privilege grants)”Host desktop capture — develop against your real screen:
xhost +local: # host, once per sessiondocker compose -f docker-compose.yml -f docker-compose.host-x11.yml up -d dev# inside dev: DISPLAY is your host display nowCaveat: grants the container your entire screen + input access. Revert with
xhost -local: and plain docker compose up -d dev.
uinput experiments:
sudo modprobe uinputdocker compose -f docker-compose.yml -f docker-compose.uinput.yml up -d devCaveat: the container can synthesize input on the host kernel.
GPU / VA-API troubleshooting
Section titled “GPU / VA-API troubleshooting”vainfoerrors insidedev→ checkLIBVA_DRIVER_NAME=iHD(set in the image) and that/dev/driis mapped (composedevices:).- Permission denied on
renderD128→.envRENDER_GIDmust equalstat -c %g /dev/dri/renderD128; rebuilddevafter changing. - No
/dev/driat all (CI, cloud VM) → doctor degrades to WARN; software encoders cover dev work but never performance claims. - Machine-specific note (Meteor Lake NUCs and newer): only the iHD driver
supports encode;
mesa-va-driversis present for completeness.
TURN sanity check
Section titled “TURN sanity check”docker compose --profile turn up -d coturn# mint an ephemeral credential by hand (docs/10#turn):u="$(($(date +%s)+600)):manual-test"p=$(printf %s "$u" | openssl dgst -sha1 -hmac "$TURN_SECRET" -binary | base64)docker run --rm --network host --entrypoint turnutils_uclient \ coturn/coturn:4.6-alpine -u "$u" -w "$p" -p 3478 -e 127.0.0.1 -n 2 127.0.0.1# Valid cred ⇒ allocation succeeds, then "403 (Forbidden IP)" on channel# bind — that's coturn's default loopback-peer block, i.e. auth WORKED.# A wrong cred fails earlier with "Cannot complete Allocation".Caches & volumes
Section titled “Caches & volumes”Named volumes keep rebuilds fast: cargo-registry, pnpm-store, ccache,
x11sock. docker compose down -v wipes them (first build after that is
slow again).
Make targets
Section titled “Make targets”doctor · agent-configure/build/test · signaling-run/test/clippy ·
web-dev/build/lint · sim-up · demo-up/down · stack-up ·
website-dev/build · docs-lint · docs-links · fmt · lint.
.vscode/tasks.json wraps the same targets — terminal and IDE never diverge.