Sign inSign up

lundog/simplex-websocket-bridge

By lundog

•Updated about 2 months ago

Runs the SimpleX Chat client in headless bot mode, exposed over a WebSocket

Image
0

1.1K

lundog/simplex-websocket-bridge repository overview

⁠SimpleX Chat WebSocket Bridge

Runs the SimpleX Chat⁠ terminal client in headless bot mode, exposed over a WebSocket so other services (or your own scripts) can send and receive SimpleX messages programmatically.

SimpleX Chat is the first messenger with no user identifiers — not even random numbers. It's fully open source, end-to-end encrypted, and metadata-resistant by design.

⁠What it does

  • Boots simplex-chat in bot mode (-p 5226 --create-bot-display-name "SimpleX Bot" --create-bot-allow-files) so the binary auto-creates a fresh profile on first start.
  • Bridges the bot's internal TCP control port to a WebSocket via websocat⁠, so any WebSocket client can drive the bot using the SimpleX bot protocol⁠.
  • Persists the bot's profile and chat history to the /data volume.

⁠Warning: Anonymous Access Allowed

The WebSocket has no built-in auth — anything that can reach the container can drive the bot.

⁠Architecture

External WebSocket client
 │
 │
 └─▶ websocat (container :5225) ──▶ ws://127.0.0.1:5226
               │
       simplex-chat (-p 5226)
               │
               └─ /data (volume, HOME)
                  └─ .simplex/   (profile DB + keys)
                      ├─ files/    (inbound, --files-folder)
                      ├─ tmp/      (--temp-folder)
                      └─ outbound/ (consumer-written, to send)

⁠Connecting programmatically

This container exposes a single WebSocket interface on port 5225. Connect any WebSocket client to that URL.

⁠Command format

The bot doesn't accept raw command strings over the WebSocket; every command must be wrapped in a small JSON envelope:

{ "corrId": "any-id-you-pick", "cmd": "/help" }
  • corrId is a correlation id you choose. The bot echoes it back in the matching response so you can pair requests and replies when multiple are in flight. Any unique string works.
  • cmd is the SimpleX terminal command, exactly as you'd type it into the simplex-chat CLI (leading slash included).

Each reply is a JSON object containing the same corrId plus a resp field with the command's result. The bot may also push unsolicited event messages (without your corrId) — incoming chats, contact updates, etc.

See the upstream SimpleX bots — sending commands⁠ guide for the full protocol, and the CLI command reference⁠ for the list of cmd values you can send.

⁠A few useful commands
  • /user — get the active user.
  • /_connect 1 — create a one-time invitation link for user ID 1.
  • /contacts — list peers who have connected.
  • /profile <name> — change the bot's display name.

Wrapped, those look like:

{ "corrId": "1", "cmd": "/user" }
{ "corrId": "2", "cmd": "/_connect 1" }
{ "corrId": "3", "cmd": "/contacts" }
{ "corrId": "4", "cmd": "/profile <name>" }

A response for the first command will look like this:

{
    "corrId": "1",
    "resp": {
        "type": "activeUser",
        "user": {
            "userId": 1,
            "profile": {
                "displayName": "SimpleX Bot"
            }
        }
    }
}

⁠Backups

The bot's profile, configuration, and chat history all live in the /data volume.

⁠File exchange

Received files live under the bot's profile dir on the /data volume, as siblings of the profile DB:

Volume subpathPurposesimplex-chat flag
.simplex/filesFiles received by the bot (inbound)--files-folder
.simplex/tmpIn-progress transfers--temp-folder

files and tmp stay co-located under .simplex/ on one filesystem — required because simplex-chat finishes a download with an atomic tmp → files rename that fails across mounts.

A consumer (another service, or the openclaw-simplex plugin) exchanges files with the bot. The two directions work differently:

  • Receiving files (inbound) — the WebSocket reports a received file by name only, which the consumer resolves against its own view of the .simplex/files dir. So the two sides only need to share that one host directory; the mountpoint can differ on each side (no matching path required).
  • Sending files (outbound) — there is no outbound dir setting and no --outbound-folder flag; sending is not a simplex-chat option at all. On send you pass a file path that the runtime resolves inside this container, so the file must already be readable here. The simplest arrangement: a consumer that shares this container's /data volume writes the file under .simplex/outbound and passes the container path /data/.simplex/outbound/…. The bot won't fetch bytes over the WebSocket.

Security: a consumer should share only what it needs — the .simplex/files subpath for inbound and .simplex/outbound for sending — never .simplex/ itself or the whole /data volume, which hold the profile database and keys.

⁠Configuration

The container is configured through environment variables (all optional):

VariableDefaultPurpose
PROFILE_DISPLAY_NAMESimpleX BotProfile display name. Applied only on first boot, when the profile is created; afterwards it lives on the persisted profile and is changed via the API.
PROFILE_PEER_TYPEbotbot creates the profile as a SimpleX bot (peerType: "bot") so peers' apps highlight commands and show command menus. human creates a plain SimpleX user (pure transport / scripted node). The marker is cosmetic; file sharing works either way. Applies only on first boot.
SMP_SERVERS(presets)Space-separated list of SMP relay addresses (smp://<fingerprint>@host) to use instead of simplex-chat's public presets. Unset = presets.
XFTP_SERVERS(presets)Space-separated list of XFTP relay addresses for file transfer. Unset = presets.
SIMPLEX_INBOUND_DIR$HOME/.simplex/filesReceived-files dir (--files-folder).
SIMPLEX_TMP_DIR$HOME/.simplex/tmpIn-progress-transfers dir (--temp-folder).
WS_MAX_MESSAGE_BYTES16777216 (16 MiB)Max WebSocket message size for the websocat bridge (-B). Above websocat's 64 KiB default so large SimpleX events/previews aren't split into partial frames (which corrupts the JSON the client parses).
INBOUND_RETENTION_HOURS(unset — never delete)If set to a positive integer, a background janitor hourly deletes received files under SIMPLEX_INBOUND_DIR older than this many hours. simplex-chat keeps received files forever and a consumer's inbound mount is usually read-only, so this lets the runtime (which owns the dir) reclaim space. tmp is left alone.

(There is no outbound dir variable — sending a file is not a simplex-chat setting; see File exchange⁠.)

SMP_SERVERS / XFTP_SERVERS accept multiple space-separated addresses, e.g. SMP_SERVERS="smp://[email protected] smp://[email protected]"; they're passed to simplex-chat as a single --server / --xftp-server argument.

The SIMPLEX_* paths are advanced knobs and default under $HOME/.simplex (which is /data/.simplex in this image, next to the profile DB). If you change them, keep inbound and tmp on the same filesystem (simplex-chat finishes a download with an atomic tmp → inbound rename that fails across mounts).

human mode note: there's no CLI flag to create a non-bot profile headlessly, so on first boot human mode answers simplex-chat's interactive display-name prompt over stdin. This is verified to work on simplex-chat v6.5.5; if a future version changes the first-run prompt, human-mode profile creation may need revisiting.

⁠Run the container
docker run -d --name simplex-websocket-bridge \
  -p 5225:5225/tcp \
  -v /path/to/simplex-volume:/data \
  --restart unless-stopped \
  lundog/simplex-websocket-bridge:latest

/data (the container HOME) holds the bot's profile and chat history in .simplex/, plus the inbound/tmp/outbound file dirs under .simplex/ — one mount, one filesystem, so simplex-chat's atomic tmp → files rename works. Sending files needs a consumer that shares this volume (or its .simplex/outbound subpath); see File exchange⁠. The WebSocket control interface is reachable at ws://localhost:5225.

⁠Repository

⁠License

This image bundles third-party programs that run as separate processes and keep their own licenses, so the image as distributed is an aggregate — SPDX-License-Identifier: MIT AND AGPL-3.0-only:

  • simplex-chat — AGPL-3.0-only, bundled unmodified from the upstream release (source⁠, pinned tag in the Dockerfile). Because it is AGPL, the image is distributed under AGPL-3.0 terms with respect to that component (including the network-use provision, AGPL §13); the corresponding source is the upstream repository at that tag.
  • websocat — MIT (source⁠).

Tag summary

Content type

Image

Digest

sha256:570ee4a91…

Size

52.1 MB

Last updated

about 2 months ago

docker pull lundog/simplex-websocket-bridge