XMPP MUC-to-MUC bridge
Use non-extra-payload algorthm for rooms that don't support it
Fix <<loop>> caused by accidental name shadowing
Detect presence from ghost using custom element

You can also use your local clone with git send-email.


This is a "double-puppeting" bridge between two XMPP MUCs, which means it keeps userlist in sync across a certain number of MUC chatrooms, and enables private messages across MUC servers. As XMPP enables to join MUC servers effortlessly, that may sound pretty useless. However, if one of those MUCs bridged is actually a gateway/transport to another protocol (like biboumi for IRC), you may find it very useful.

cheogram-muc-bridge is currently alpha-quality software. Please try it out and contribute feedback and patches. See the list of Known issues, Getting help, and Contributing section.


#Setup from packages

cheogram-muc-bridge is currently not packaged for any distribution.

Alternatively, if you have a server with root access, and no web/XMPP services configured, you can setup cheogram-muc-bridge using ansible-selfhosted recipes (alpha software), like so:

# apt install ansible
# git clone https://codeberg.org/southerntofu/ansible-selfhosted infra
# cat << EOF > infra/config.yml
hostname: "example.com"
services: [ "mucbridge" ]
  nick: "my-own-bridge"
  host: "bridge.example.com"
  chans: # List of JIDs to bridge together
    - [ { jid: "#foo@biboumi.example.net", tag: "net", nickChars: "Some \"a-z[]A-Z\"" }, { jid: "foo@muc.example.ml", tag: "ml", nickChars: "None Text" } ]
# cd infra
# roles/deploy.sh mucbridge

This assumes DNS records are already pointing to your server for example.com and bridge.example.com, and you don't have a firewall running.

#Setup manually from sources

#Step 1: Install cheogram-muc-bridge

You need to have cabal (the Haskell build system) installed, as well as ghc and c2hs. In addition, you need the libxml2, gnutls, zlib and gsasl libraries installed. On debian, you can set it all up from the following packages: cabal-install libxml2-dev libgsasl7-dev gnutls-dev c2hs zlib1g-dev

Download the sources, build with cabal (Haskell build system). The built program will be in dist/build/gateway/gateway:

$ git clone https://git.singpolyma.net/cheogram-muc-bridge
$ cd cheogram-muc-bridge
$ cabal update # Refresh packages list
$ cabal install --only-dependencies
$ cabal configure && cabal build

The built program, located in dist/build/gateway/gateway can then be copied to wherever you desire on your system, for example /usr/local/bin/cheogram-muc-bridge.

#Step 2: Initialize the database

Now you can initialize the SQLite database using the sqlite3 CLI interface (sqlite3 package in Debian):

$ sqlite3 sqlite.db < schema.sqlite

ATTENTION: In addition to having write permissions to the file, SQLite expects to have write permissions on the parent folder. So you need to make sure the parent folder is readable by cheogram-muc-bridge as well! Otherwise, you may spot SQLite3 returned ErrorReadOnly while attempting to perform step: attempt to write a readonly database despite the SQLite DB having correct permissions.

#Step 3: Make a new domain name

cheogram-muc-bridge requires its own XMPP domain to operate. If you have no other XMPP virtual hosts or component on a specific domain, you may reuse it to host cheogram-muc-bridge. Otherwise, you will need to add a new record pointing to the server where your Jabber/XMPP server is hosted.

Note: It is assumed cheogram-muc-bridge will run on the same machine as your Jabber/XMPP server, and connection can be established via localhost. Setting up the two services on different machines is beyond the scope of this document.

#Step 4: Register as component to your Jabber/XMPP server

Now, you need to generate a passphrase which will be a shared secret between cheogram-muc-bridge and your Jabber/XMPP server, for example using cat /dev/urandom | tr -dc 'A-Za-z0-9@#$%&_+=' | head -c 64. This secret will be used in the configuration file, as explained in the Configuration section. Now, how to configure the component depends on the server you use. In both cases, you need a valid TLS certificate for the component domain you intend to use.

ATTENTION: In addition to the configuration provided here, you need to make sure that cheogram-muc-bridge is reachable via your Jabber/XMPP server using a valid TLS certificate! How to setup a TLS certificate is beyond the scope of this document, as it is usually a requirement to operate your Jabber/XMPP server.

Component "mucbridge.example.com"
  component_secret = "SECRET"

  ssl = {
    certificate = "/etc/letsencrypt/live/mucbridge.example.com/fullchain.pem",
    key = "/etc/letsencrypt/live/mucbridge.example.com/privkey.pem"

In the section that says: '{listen,' add those two lines:

    {5347, ejabberd_service, [{host, "mucbridge.example.com",
                               [{password, "SECRET"}]}]},

TODO: That is according to a tutorial found on ejabberd.im, but it does not mention TLS support! Please someone who knows ejabberd well propose better guidance.

#Step 5: Configure

Configuration is explained in the Configuration section of this README. You can find an example config file.

#Step 6: Run

Now, you should be able to run cheogram-muc-bridge /path/to/config.dhall. If you would like to deploy a service to make sure cheogram bridge is started when your machine boots, the instructions depend on your init system. In the examples below, we assume:

  • the program is located in /usr/local/bin/cheogram-muc-bridge
  • the configuration file is /etc/cheogram/muc-bridge.dhall
  • cheogram-muc-bridge should run as user/group cheogram/cheogram

The systemd service file should be placed in /etc/systemd/system/cheogram-muc-bridge.service:

Description=cheogram-muc-bridge MUC2MUC bridge

ExecStart=/usr/local/bin/cheogram-muc-bridge /etc/cheogram/muc-bridge.dhall


Every time it is modified (or the first time it is created), you need to run systemctl daemon-reload. Then, you can make sure the service is enabled at boot time with systemctl enable cheogram-muc-bridge and/or start it immediately with systemctl start cheogram-muc-bridge.


You can find an example configuration file in the config.dhall.example file. It can have the following options:

  • nick: the nickname the bridge should adopt when joining channels
  • componentJid: the domain name name where cheogram-muc-bridge should be reachable; requires a valid TLS certificate as well as valid Jabber/XMPP server configuration, as explained in the Setup section
  • secret: the secret key used to authenticate cheogram-muc-bridge as component to identify against the Jabber/XMPP server
  • server: a dictionary with the options to connect to the Jabber/XMPP server
    • host: the domain name or IP address of the server
    • port: the port number the server is running on
  • db: the path to the SQLite database, which must be initialized as explained in the Setup section
  • mucs: a list of different channel groups, each containing containing a list of individual MUC chatrooms to bridge together, each with the following settings
    • jid: the JID address to reach the room
    • tag: the suffix to append to members of this room, automatically wrapped inside [] if the nickChars allows it on the other sides; illegal characters are replaced with _
    • nickChars: the explicit list of characters to allow as nicknames on this chatroom (usually Some "a-zA-Z[]" for IRC); None Text does not place any special restrictions
    • nickLength: the maximum length of a nick in this MUC, or None Natural for no limit.

#Known issues (TODO)

cheogram-muc-bridge is alpha-quality software. Just because we haven't found a bug yet doesn't mean it's not there, so don't hesitate to report bugs so they can be fixed. Below, you will find a list of issues we are aware of:

  • in some circumstances, individual chatroom members may be bridged several times back and forth, resulting in nicknames like user[foo][bar][foo]; this bug is currently being investigated (July 2021)
  • the SQLite database is not automatically created or initialized, so make sure to follow the setup guide provided in this README
  • when joining a biboumi IRC gateway chatroom where the nick is already taken, no error is provided (upstream ticket)
  • when the tag on a chan is deemed illegal by another chan's nickChars, no warning is currently produced
  • nickChars = None Text does not enforce that illegal JID characters are prevented; in the future, such characters should be properly escaped as explained in XEP-0106 or entirely forbidden

#Getting Help

Part of the soprani.ca family of projects.

If you have any questions about this project, or wish to report a bug, please send email to: dev@singpolyma.net

You may come discuss the project in our chatroom: xmpp:discuss@conference.soprani.ca?join


If you have code or patches you wish to contribute, the maintainer's preferred mechanism is a git pull request. Push your changes to a git repository somewhere, for example:

git remote rename origin upstream
git remote add origin git@git.sr.ht:~yourname/cidfd
git push -u origin master

Then generate the pull request:

git fetch upstream master
git request-pull -p upstream/master origin

And copy-paste the result into a plain-text email to: dev@singpolyma.net

You may alternately use a patch-based approach as described on https://git-send-email.io

Contributions follow an inbound=outbound model -- you (or your employer) keep all copyright on your patches, but agree to license them according to this project's COPYING file.


This program is released under the AGPLv3 license. You can find a copy in the COPYING file.