Skip to content

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.

Terminal window
git clone <repo> fjarr && cd fjarr
cp .env.example .env # then check RENDER_GID matches your host:
stat -c %g /dev/dri/renderD128 # → RENDER_GID in .env
code . # "Reopen in Container"
# post-create runs pnpm install, cargo fetch, cmake configure, doctor
make doctor

Or without VS Code: docker compose up -d dev robot-sim, then docker compose exec dev bash.

ServiceProfilePurpose
devdefaultthe toolbox: C++/GStreamer 1.24 + Rust + Node 22 (see the Dockerfile’s commented package groups)
robot-simdefaultXvfb fake robot desktop at :99 (openbox + moving apps); watch it at http://localhost:6080 (noVNC)
fjarr-serverdemo, stackthe production sidecar image, built from signaling/ only
demo-backenddemothe TS “customer backend” beside the sidecar
demo-robotdemothe C++ “customer robot” capturing robot-sim
demo-dashboarddemoVite dev server on http://localhost:5173
coturnturnTURN 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.

make doctor.devcontainer/doctor.sh. Checks, and what a failure means:

CheckOn failure
toolchains (cmake/cargo/node/…)image build incomplete — rebuild dev
GStreamer elements (webrtcbin, vah264enc, ximagesrc, …)missing plugin package — see Dockerfile groups
x264enc absentif present: GPL plugin leaked in — ADR-0011 violation, fix the image
/dev/dri accessibleRENDER_GID in .env ≠ host render group; fix + docker compose build dev
vainfo encode entrypoints + vah264enc smoke pipelineiHD driver problem — see GPU troubleshooting
DISPLAY=:99 + ximagesrc capturerobot-sim not up, or stale X socket — docker compose restart robot-sim
libei/pipewire/libevdev pkg-configdev packages missing from image
/dev/uinputWARN by default; opt in via the uinput override
webrtcsinkWARN 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:

Terminal window
xhost +local: # host, once per session
docker compose -f docker-compose.yml -f docker-compose.host-x11.yml up -d dev
# inside dev: DISPLAY is your host display now

Caveat: grants the container your entire screen + input access. Revert with xhost -local: and plain docker compose up -d dev.

uinput experiments:

Terminal window
sudo modprobe uinput
docker compose -f docker-compose.yml -f docker-compose.uinput.yml up -d dev

Caveat: the container can synthesize input on the host kernel.

  • vainfo errors inside dev → check LIBVA_DRIVER_NAME=iHD (set in the image) and that /dev/dri is mapped (compose devices:).
  • Permission denied on renderD128.env RENDER_GID must equal stat -c %g /dev/dri/renderD128; rebuild dev after changing.
  • No /dev/dri at 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-drivers is present for completeness.
Terminal window
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".

Named volumes keep rebuilds fast: cargo-registry, pnpm-store, ccache, x11sock. docker compose down -v wipes them (first build after that is slow again).

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.