Skip to content

Bridges

Bridges are small companion programs, maintained by the Vexillum developers, that connect a Vexillum reflector to other networks, or connect two Vexillum modes to each other. Each bridge is a separate program with its own repository under repo.kc1awv.net/Vexillum. None of them is built into Vexillum.

Vexillum has no special “peer” or “interlink” concept. A bridge logs into a Vexillum instance the way an ordinary client does:

  • into a DMR instance as a Homebrew repeater,
  • into a YSF or NXDN instance as a hotspot,
  • into a DExtra instance as a linking client,
  • into a VAFM instance as a VAFM client.

That means Vexillum treats the bridge as a normal connected client until you designate it. You don’t need to change the Vexillum configuration to allow it beyond what any client needs, such as the DMR instance password. It also means the bridge has to follow the same rules as any other client. For example, a DMR instance only forwards group calls to its configured Talk TG on slot 2, so a DMR bridge’s -vex-talkgroup must match that value exactly.

Bridge Connects Vexillum to Audio handling
vex-dmr-bridge One or more Homebrew/MMDVM DMR networks (Brandmeister, TGIF, FreeDMR, FreeSTAR SystemX, DMR+, another Vexillum or urfd) Frames relayed unchanged
vex-openbridge-bridge Brandmeister, over its OpenBridge server-to-server protocol Frames relayed unchanged
vex-xlx-bridge A module on an XLX or urfd reflector (D-Star interlink) Frames relayed unchanged
vex-dmr-ysf-bridge Vexillum DMR ↔ Vexillum YSF AMBE frames repacked, no transcoding
vex-nxdn-ysf-bridge Vexillum NXDN ↔ Vexillum YSF AMBE frames repacked, no transcoding
vafm-homebrew-bridge Vexillum VAFM ↔ a Homebrew DMR master Full transcode (Opus ↔ AMBE+2)

A YSF instance sends every voice frame it receives to every connected session, including bridges. If you run both vex-dmr-ysf-bridge and vex-nxdn-ysf-bridge against the same YSF instance, you get a three-way conversation:

DMR  <── vex-dmr-ysf-bridge ──>  YSF  <── vex-nxdn-ysf-bridge ──>  NXDN

DMR and NXDN users reach each other through YSF, so no direct DMR ↔ NXDN bridge is needed. Add vex-dmr-bridge or vex-openbridge-bridge to the same DMR instance, and outside DMR networks join the conversation too.

No bridge publishes binaries yet, so you build them from source. The bridges are written in Go and need Go 1.25 or newer. Each one builds the same way:

git clone https://repo.kc1awv.net/Vexillum/vex-dmr-bridge.git
cd vex-dmr-bridge
go build ./cmd/vex-dmr-bridge

Replace the repository and command name with the bridge you want. The only exception is vafm-homebrew-bridge, which needs native audio codec libraries and cgo. See its page.

Every bridge takes command-line flags, and -help prints the full list. None of them uses a configuration file.

Passwords and passphrases are never passed as flags. Each bridge reads them from a named environment variable, and a flag like -vex-password-env lets you change the variable’s name. If a required secret is missing, the bridge exits at startup with a message naming the variable.

Vexillum credits “last heard” activity to the callsign the connecting session registered with. If a bridge registers with a real operator’s callsign, every relayed call looks like it came from that operator. Give each bridge a distinct identity (for example, a callsign with a suffix, or a dedicated DMR ID) instead of your personal one.

This matters again when you designate the bridge: a designation matches every session with that callsign or DMR repeater ID on the instance. See One designation, one session.

Vexillum can guess which sessions are bridges, but it doesn’t act on the guess. Until you designate a bridge’s session on the admin Bridges page, Vexillum treats it as an ordinary station:

  • it’s listed and counted as a connected station,
  • calls it relays aren’t labelled as bridged,
  • the activity feed, including the VexDV public directory, reports it as a client, and reports its calls as if the talker had connected directly.

Once designated, the session is reported as a bridge under the label you give it, and every call it carries says “via” that label. That way, a call bridged from DMR onto YSF reads as the same conversation rather than a second talker.

To designate a bridge:

  1. Start the bridge, then open Bridges in the admin interface.
  2. Under Detected Bridge Connections, find the bridge’s session. Vexillum lists any session connected from a loopback address, and any DMR session whose software ID starts with vex-. Rows marked not designated have a Designate button.
  3. Click Designate. It fills in the instance, and matches by the bridge’s callsign, or on DMR by its repeater ID.
  4. Type a Label and click Add. Labels are published, so choose something readers will understand, such as Brandmeister TG 3170603 or DMR-YSF.

A bridge that runs on another host doesn’t connect from loopback, so it isn’t detected. Add its designation by hand in the same form, matching the callsign or DMR repeater ID the bridge presents to Vexillum.

Designate every Vexillum instance a bridge connects to. A cross-mode bridge has a session on each side, so vex-dmr-ysf-bridge needs one designation on the DMR instance and one on the YSF instance. Give both the same label. That’s how Vexillum knows the two sessions are one bridge, and it’s what lets it link each call the bridge relays to the call it came from. A DMR call relayed onto YSF is then reported as one transmission heard on two instances, not as two talkers. Chains work too: in the cross-mode mesh, a DMR call relayed to YSF and then on to NXDN is reported as one transmission on all three. Calls from outside the server, such as Brandmeister calls, are labelled “via” their bridge but aren’t linked to anything.

Tick Required if the instance shouldn’t be considered healthy without the bridge. A required bridge that isn’t connected marks its instance degraded in the admin interface and in the activity feed.

A bridge has exactly one session on each instance it joins, so each designation should match exactly one session. If the bridge shares a callsign or DMR ID with anyone else connected to the same instance, typically your own hotspot or radio, the designation matches them too:

  • their calls are reported as coming through the bridge rather than from them, and
  • Vexillum can’t link the bridge’s relayed calls to those calls, because it never treats a bridge’s own traffic as the source of what it relays. Every leg of the transmission shows up separately again.

The Designated Bridges table warns about this with matches N sessions next to the connection status. To fix it, restart the bridge with its own identity (a DMR ID and callsign that nothing else uses on that instance), remove the designation, and designate the bridge’s detected connection again. On DMR, Designate matches by repeater ID, so a dedicated ID is what keeps your hotspot out of it.

Each bridge serves GET /status on -status-addr. It returns JSON with the state of each leg of the bridge:

{
  "vex": {"state": "running", "last_event": "connected", "updated_at": "..."},
  "remote": {"state": "running", "last_event": "connected", "updated_at": "..."}
}

This is for local monitoring (curl, uptime checks, systemd health scripts). It does not appear in the Vexillum admin interface.

The bridges run in the foreground and log to standard error, so they work well under systemd. A minimal unit looks like this:

[Unit]
Description=Vexillum DMR network bridge
After=network-online.target
Wants=network-online.target

[Service]
EnvironmentFile=/etc/vex-dmr-bridge.env
ExecStart=/usr/local/bin/vex-dmr-bridge \
  -vex-master 127.0.0.1:62031 \
  -vex-id 9990001 \
  -vex-callsign N0CALL-B \
  -vex-talkgroup 7 \
  -remote "name=bm,master=3101.brandmeister.network:62031,id=3120001,callsign=N0CALL,talkgroup=91" \
  -status-addr 127.0.0.1:8099
Restart=on-failure
DynamicUser=yes

[Install]
WantedBy=multi-user.target

Put the passwords in the EnvironmentFile (for example VEX_DMR_PASSWORD=...), and make that file readable only by root.

All bridges are licensed GPL-2.0-or-later. Each repository’s NOTICE.md and THIRD_PARTY_NOTICES.md credit the projects they draw on.