# Node Identity NODE_ID=node-001 NODE_NAME="My Radio Node" NODE_LAT=0.0 NODE_LON=0.0 # MQTT — point to your C2 server # # Post-cutover (MQTT-PUBLIC-AUTH-PLAN.md dynsec revision): there is no shared # node login any more. The node authenticates as username=NODE_ID, # password= automatically — nothing to set # here for that; the api_key is provisioned via MQTT after an admin approves # the node (see credentials.json) and does not go in this file. # # Local/dev, pointed at a plaintext broker on :1883: leave MQTT_TLS unset. MQTT_BROKER=localhost MQTT_PORT=1883 MQTT_TLS=false # Production, pointed at the public broker (real Let's Encrypt cert, default # CA verification — do not disable it): # MQTT_BROKER=mqtt. # MQTT_PORT=8883 # MQTT_TLS=true # DEPRECATED / REMOVED post-cutover — the shared "drb-node" login these # backed no longer exists on the server (dynsec creates only per-node # clients, keyed by api_key; see Server/drb-c2-core/app/internal/dynsec.py). # Leave unset for any node pointed at a cut-over broker. Only meaningful as a # legacy fallback if MQTT_BROKER still points at a pre-cutover broker running # mosquitto's old password_file auth. # MQTT_USER=drb-node # MQTT_PASS=change-me-node # C2 server for audio upload (leave blank to disable upload) C2_URL=http://localhost:8888 # API key is provisioned automatically via MQTT after admin approves the node # Icecast (local container — usually no need to change) # Live listening only. Call recording and Discord voice use PulseAudio instead. ICECAST_SOURCE_PASSWORD=hackme ICECAST_ADMIN_PASSWORD=admin ICECAST_HOST=localhost ICECAST_PORT=8000 ICECAST_MOUNT=/radio # PulseAudio capture (usually no need to change) # Monitor of the drb_sink null sink that Liquidsoap writes into. PULSE_SOURCE=drb_sink.monitor # Seconds to wait for the shared PulseAudio socket before giving up and retrying. PULSE_WAIT_TIMEOUT=30 # --- Call segmentation ------------------------------------------------------- # Recording boundaries come from the AUDIO, not the control channel: a recording # starts at voice onset and ends after this many seconds of silence actually # heard in the stream. Transmissions on the same talkgroup separated by less # than this stay in ONE recording, so back-and-forth traffic is one file. # Tune from the "measured trailing silence" line logged on every close. CALL_SILENCE_TIMEOUT=3.0 # dBFS (RMS over one ~46ms chunk) below which audio counts as silence and the # recording is allowed to close. # # This does NOT need calibrating against your radio's noise floor. Between # transmissions the capture is the monitor of a PulseAudio *null sink*, which # emits DIGITAL silence: measured on a live node it sits at about -91 dBFS — # one least-significant bit of a 16-bit sample — while speech averages about # -18 dBFS. Anything from roughly -70 to -40 behaves identically. Only change # this if you have replaced the audio path with something that has a real # analog noise floor. CALL_SILENCE_THRESHOLD_DB=-50 # DEPRECATED as a primary control. Used ONLY when PulseAudio capture is not # producing audio, where the old control-channel state machine takes over so # the node still reports radio activity (with no recordings) while its audio # path is broken. CALL_IDLE_TIMEOUT=3 # Seconds of audio kept past a CONTROL-CHANNEL-derived boundary — a talkgroup # change, or a close in the fallback mode above. Buffered audio lags the # control channel by ~1.5s (grant-to-speech offset measured 0.84-1.62s), so # cutting at the exact control-channel timestamp clipped the last words of the # outgoing call. Does NOT apply to the normal end of a call any more; that # boundary comes from the audio and needs no pad. Safe to be generous — the # extra is trimmed off again before upload. CALL_TAIL_PAD_SECONDS=3.0 # Strip leading/trailing dead air before upload. Recordings deliberately # over-capture at both ends, and silence costs Whisper spend and makes it # hallucinate text that was never spoken. Trimming is a sample-offset slice of # the buffered PCM (no re-encode) and only ever touches the head and tail, with # a guard margin so no syllable is clipped. Set to false to upload raw audio. TRIM_SILENCE=true # dBFS (RMS) below which audio counts as silence when trimming the ends. Kept # stricter than CALL_SILENCE_THRESHOLD_DB on purpose. TRIM_SILENCE_THRESHOLD_DB=-40 # Seconds of audio kept either side of detected speech. TRIM_SILENCE_GUARD_SECONDS=0.25 # OP25 container (usually no need to change) OP25_API_URL=http://localhost:8001 OP25_TERMINAL_URL=http://localhost:8081 # DEBUGGING AID, NOT A DEPLOYMENT OPTION. Both OP25's control API (:8001) and # its HTTP terminal (:8081) have NO authentication, so they are bound to # 127.0.0.1 by default — reachable only from other containers on this same # host (they share its network namespace), not from the site's LAN. Setting # this to true rebinds both to 0.0.0.0, exposing unauthenticated OP25 # start/stop/config-rewrite and the raw terminal to anyone on that LAN. Only # for local development off a real node; leave false everywhere else. OP25_DEBUG_EXPOSE=false # --- Local dashboard / API login --------------------------------------------- # Protects the node's local dashboard (port 80) and JSON API. The node is # reachable by anyone on whatever site's LAN it's deployed to, so this MUST be # changed before the node leaves the bench — the default below is flagged at # every startup in the logs until it's changed. DASHBOARD_USERNAME=admin DASHBOARD_PASSWORD=CHANGE-ME-drb-default # Container registry — set these to pull pre-built images instead of building locally. # Must match the DOCKER_ORG variable and repo name configured in Gitea. # Leave blank to always build locally. IMAGE_REGISTRY=git.vpn.cusano.net DOCKER_ORG= DOCKER_REPO=