# Running jibri locally against a docker-jitsi-meet deployment This directory contains a script and a docker-compose override that let you run jibri **from this source tree** while the rest of the Jitsi stack (prosody, jicofo, jvb, web) runs in docker via [docker-jitsi-meet](https://github.com/jitsi/docker-jitsi-meet). Useful for iterating on jibri code changes without rebuilding the docker image. ## Files - `run-jibri-local.sh` — generates `jibri.conf` + `logging.properties` from your `docker-jitsi-meet/.env`, builds jibri if needed, and launches it. - `prosody.override.yml` — publishes prosody's port 5222 on `127.0.0.1` so the host-side jibri can reach it. The default compose only `expose:`s this port inside the docker network. ## Prerequisites - `docker` + `docker compose` - `mvn` and Java 17 (to build jibri) - `ffmpeg` on `PATH` (jibri shells out to it for capture) - A clone of [docker-jitsi-meet](https://github.com/jitsi/docker-jitsi-meet) (defaults to `~/dev/docker-jitsi-meet`; override with `DOCKER_JITSI_DIR`) - A `chromedriver` binary at the jibri repo root, matching your installed Chrome milestone (download from ) ## Step by step ### 1. Set up docker-jitsi-meet ```sh cd ~/dev/docker-jitsi-meet cp env.example .env ./gen-passwords.sh mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri} ``` ### 2. Edit `.env`: enable recording and set `PUBLIC_URL` Two changes in `~/dev/docker-jitsi-meet/.env`: **Enable recording.** This is what tells prosody to provision the `jibri` and `recorder` accounts (using `JIBRI_XMPP_PASSWORD` / `JIBRI_RECORDER_PASSWORD` from `gen-passwords.sh`) and what makes the web UI surface the record button. Without it, jibri can't even log into prosody. ```sh ENABLE_RECORDING=true ``` **Point `PUBLIC_URL` at your LAN IP.** Jibri opens the conference URL in Chrome from the host. `localhost` won't do, because Chrome's WebRTC needs an address that resolves the same way for jibri *and* the other browsers joining the call. Use the host's LAN address. Find your local IP: ```sh # macOS ipconfig getifaddr en0 # Linux hostname -I | awk '{print $1}' ``` Then set (HTTP/HTTPS port already exist in env.example — adjust if you moved them): ```sh HTTP_PORT=8000 HTTPS_PORT=8443 PUBLIC_URL=https://192.168.1.42:8443 # ← your IP and the HTTPS_PORT ``` The script copies `PUBLIC_URL` into jibri's `base-url`, so jibri opens `https://192.168.1.42:8443/` instead of the default `https://meet.jitsi/`. ### 3. Bring the stack up with prosody 5222 published The host-side jibri talks to prosody over XMPP, so we need 5222 reachable from the host. Apply the override from this directory: ```sh cd ~/dev/docker-jitsi-meet docker compose \ -f docker-compose.yml \ -f ~/dev/jibri/doc/local-run/prosody.override.yml \ up -d ``` Confirm prosody is listening on the host: ```sh nc -zv 127.0.0.1 5222 # → "succeeded!" ``` ### 4. Build jibri ```sh cd ~/dev/jibri mvn -B -DskipTests clean package ``` The script will also build automatically if no jar is found in `target/`. ### 5. Drop a matching `chromedriver` next to the jibri repo ```sh # look up your Chrome major version /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version # fetch the matching chromedriver and place it at ~/dev/jibri/chromedriver # (see https://googlechromelabs.github.io/chrome-for-testing/) chmod +x ~/dev/jibri/chromedriver ``` ### 6. (Linux only) Set up audio routing for ffmpeg Jibri's default ffmpeg config captures audio from the ALSA PCM `plug:bsnoop`, which is a custom loopback device defined in `/etc/jitsi/jibri/asoundrc` and backed by the kernel `snd-aloop` module. The .deb postinst sets this up for the system `jibri` user; if you're running locally as your own user, you need to do it yourself, or switch to PulseAudio capture. **Option A — ALSA loopback (matches the deb):** ```sh # 1. Load the kernel loopback module (creates ALSA card "Loopback") sudo modprobe snd-aloop # Persist across reboots echo snd-aloop | sudo tee /etc/modules-load.d/snd-aloop.conf # 2. Drop jibri's asoundrc as your user's ALSA config cp ~/dev/jibri/resources/debian-package/etc/jitsi/jibri/asoundrc ~/.asoundrc # Verify aplay -L | grep bsnoop arecord -D plug:bsnoop -f cd -d 1 /tmp/t.wav && echo OK ``` Caveat: this changes your default ALSA device to a loopback, and on a modern Ubuntu desktop Chrome routes audio through PulseAudio/PipeWire — so even with `bsnoop` defined, Chrome won't play into the loopback unless you also force Chrome to ALSA (e.g. `PULSE_SERVER=/dev/null`). **Option B — PulseAudio capture (recommended on a desktop dev box):** Skip the asoundrc/snd-aloop setup entirely and override the audio knobs when invoking the script: ```sh JIBRI_AUDIO_SOURCE=pulse \ JIBRI_AUDIO_DEVICE=default \ ~/dev/jibri/doc/local-run/run-jibri-local.sh ``` `default` reads from Pulse's default source. To capture system audio specifically, find the right monitor source with `pactl list short sources` and pass it as `JIBRI_AUDIO_DEVICE` (e.g. `alsa_output.pci-…analog-stereo.monitor`). ### 6b. (macOS only) Capture the screen instead of the camera Jibri's default macOS ffmpeg input is `-i 0:0` — whatever avfoundation device 0 is (usually a camera + the mic). To record the conference you want a **screen** device for video and a **loopback** device for audio. 1. **Grant Screen Recording permission** to the terminal app you launch the script from (System Settings → Privacy & Security → Screen Recording), then fully quit and relaunch the terminal. Without it avfoundation silently produces black frames. 2. **Install a loopback audio device**, e.g. [BlackHole](https://github.com/ExistentialAudio/BlackHole) (`brew install blackhole-2ch`), and route the call audio into it: Audio MIDI Setup → `+` → *Create Multi-Output Device* → check both BlackHole and your speakers, then select that multi-output device as the system sound output. (Selecting BlackHole alone also works, but you won't hear anything.) 3. **Find the device indices**: ```sh ffmpeg -f avfoundation -list_devices true -i "" # e.g. video: [2] Capture screen 0 audio: [2] BlackHole 2ch ``` 4. **Run with the indices**: ```sh JIBRI_MAC_VIDEO_DEVICE=2 JIBRI_MAC_AUDIO_DEVICE=2 \ ~/dev/jibri/doc/local-run/run-jibri-local.sh ``` Note avfoundation screen devices capture the *whole* display at native (retina) resolution — there is no per-window capture in ffmpeg on macOS. The script scales the output down to `JIBRI_RECORDING_RESOLUTION` width, but whatever else is on that screen ends up in the recording, so keep the jibri Chrome window in front (or point it at a secondary display, e.g. `Capture screen 1`). ### 7. Run jibri ```sh ~/dev/jibri/doc/local-run/run-jibri-local.sh ``` You should see: ``` config: /Users/you/dev/jibri/.local/jibri.conf jar: /Users/you/dev/jibri/target/jibri-*-jar-with-dependencies.jar chromedriver: /Users/you/dev/jibri/chromedriver xmpp: 127.0.0.1:5222 (domain=meet.jitsi) … INFO: Jibri starting up … … INFO: Connecting to xmpp environment on 127.0.0.1 … … INFO: Joined MUC: jibribrewery@internal-muc.meet.jitsi ``` Now open `https://192.168.1.42:8443/` in two browsers, click the record button — jibri will pick up the start IQ and join the call. ## Where things land Generated files live under `~/dev/jibri/.local/`: | File | Purpose | |---|---| | `jibri.conf` | HOCON config built from `.env` | | `logging.properties` | JUL config (console + ffmpeg/browser file handlers) | | `recordings//*.mp4` | recorded files | | `ffmpeg.0.txt` | ffmpeg stderr (JUL captures it; not on console) | | `browser.0.txt` | chrome browser logs | | `chromedriver.log` | chromedriver's own log | ## Configuration knobs All overridable as env vars when invoking the script: | Var | Default | Notes | |---|---|---| | `JIBRI_DIR` | resolved from script path | jibri repo root | | `DOCKER_JITSI_DIR` | `$HOME/dev/docker-jitsi-meet` | source of `.env` | | `XMPP_HOST` | `127.0.0.1` | where prosody is reachable | | `XMPP_PORT` | `5222` | matches `.env` if set there | | `JIBRI_INSTANCE_ID` | `jibri-local-$$` | shows up as MUC nickname | | `CHROMEDRIVER` | `$JIBRI_DIR/chromedriver` | path to chromedriver binary | | `JIBRI_AUDIO_SOURCE` | `alsa` | Linux: ffmpeg `-f` for audio (`alsa`, `pulse`) | | `JIBRI_AUDIO_DEVICE` | `plug:bsnoop` | Linux: ffmpeg audio input device (e.g. `default` for pulse) | | `JIBRI_MAC_VIDEO_DEVICE` | unset | macOS: avfoundation video index (use a `Capture screen N` device) | | `JIBRI_MAC_AUDIO_DEVICE` | unset | macOS: avfoundation audio index (use a loopback device, e.g. BlackHole) | ## Troubleshooting **`permission denied while trying to connect to the docker API at unix:///var/run/docker.sock`** — Linux only; your user isn't in the `docker` group. Fix: ```sh sudo usermod -aG docker $USER newgrp docker # apply the new group in this shell, or log out/in docker ps # verify ``` Avoid `sudo docker compose …` — files written to `~/.jitsi-meet-cfg` end up root-owned and bite you later. **`cannot reach prosody at 127.0.0.1:5222`** — you brought up the stack without `prosody.override.yml`. Re-run step 3. **`This version of ChromeDriver only supports Chrome version N`** — your chromedriver and Chrome milestones don't match. Re-fetch chromedriver for your Chrome version (step 5). **Jibri opens `https://meet.jitsi/` instead of your IP** — `PUBLIC_URL` isn't set in `.env`, or you didn't restart the script after editing `.env`. **`No such file or directory: /var/log/jitsi/jibri/chromedriver.log`** — older jibri pinned that path unconditionally. Pull a tree where `webdriver.chrome.logfile` is overridable, or `mkdir -p` the path with write permissions. **ffmpeg quits with `Unknown PCM bsnoop` / `cannot open audio device plug:bsnoop`** on Linux — you don't have the ALSA loopback set up. Either follow step 6 Option A (snd-aloop + `~/.asoundrc`) or skip ALSA and use Pulse via `JIBRI_AUDIO_SOURCE=pulse JIBRI_AUDIO_DEVICE=default`. **ffmpeg quits abruptly with "Error opening output files: Invalid argument"** on macOS — usually an avfoundation arg conflict. Inspect `~/dev/jibri/.local/ffmpeg.0.txt` for the real ffmpeg stderr. Note that the default macOS ffmpeg input is `-i 0:0` (camera + system mic), so the resulting mp4 records your camera, **not** the chrome window — see step 6b for screen capture via `JIBRI_MAC_VIDEO_DEVICE`/`JIBRI_MAC_AUDIO_DEVICE`. **Black video on macOS** — the terminal app that launched the script lacks the Camera (default `0:0` input) or Screen Recording (screen-capture input) permission. avfoundation doesn't error on a denied device; it delivers black frames. Grant the permission in System Settings → Privacy & Security and fully restart the terminal app. **Black video on Linux** — you're on a Wayland session (`echo $XDG_SESSION_TYPE`), where `x11grab` cannot see the screen and records black. Either log into an Xorg session, or run a dedicated virtual display and point both Chrome and ffmpeg at it: ```sh Xvfb :1 -screen 0 1280x720x24 & DISPLAY=:1 ~/dev/jibri/doc/local-run/run-jibri-local.sh ``` (the second part — overriding ffmpeg's hardcoded `-i :0.0+0,0` grab target — currently requires editing `input-linux` in the generated conf).