* feat: add simplex-support-bot-light * feat: package simplex-support-bot-light for docker * docs: document simplex-support-bot-light * fix: make a missing config actionable and bounded * docs: note the attached compose Ctrl+C behaviour * refactor: use the library for startup and custom data * refactor: drop the display-name workaround * style: shorten the startup comment * build: build the core and the library into the image * docs: correct where the image type rule comes from * fix: log what the core said a command got wrong * build: run the container as the operator's uid * docs: keep the container uid in .env * docs: tighten the README
simplex-support-bot-light
A SimpleX Chat bot that adds a roster of people to incoming business chats.
Anyone who connects to the bot's address gets a business chat with a welcome message, and every active roster member is added to it. People join the roster themselves, from a command menu in a separate roster group.
Docker
From apps/simplex-support-bot-light:
cp bot-config/config.toml.example bot-config/config.toml # required; edit before starting
printf 'USER_UID=%s\nUSER_GID=%s\n' "$(id -u)" "$(id -g)" > .env # see Ownership
chmod 0700 state
docker compose up --build -d
docker compose logs -f support-bot-light
Use the template in bot-config/, whose paths are container paths, not the
top-level one. Place the avatar beside it if bot.image is set.
| Path | Mount | Notes |
|---|---|---|
./bot-config |
/etc/support-bot-light (read-only) |
bot.image resolves against this directory. |
./state |
/data |
All bot state. bot.db_prefix must point here. |
The monitoring endpoint is published on 127.0.0.1:8080, and the container
config must set health.host = "0.0.0.0", as the template does.
Run detached. Under an attached docker compose up, Ctrl+C stops the container
but compose re-attaches it; press Ctrl+C twice or use
--abort-on-container-exit.
State directory
./state holds the bot's identity and address. Deleting it produces a new
address and a new roster group, and every roster member must repeat the
handshake. Back it up.
It must be owned by the uid the container runs as, set in .env. Both ids
default to 1000; root is not supported. chmod 0700 it on a shared host, since
the databases hold the bot's identity keys.
Manual installation
uv venv && uv pip install -e ../../packages/simplex-chat-python && uv pip install -e '.[dev]'
cp config.toml.example config.toml
uv run support-bot-light --config config.toml
The library is installed from this repository, since the APIs the bot uses are
unreleased. libsimplex is downloaded on first use unless SIMPLEX_LIBS_DIR
points at a local build.
--config defaults to config.toml in the working directory. Ctrl+C stops
the bot; a second Ctrl+C exits immediately.
Configuration
config.toml.example is the committed template; config.toml is gitignored.
| Key | Required | Description |
|---|---|---|
bot.display_name |
yes | Name shown to anyone who connects. |
bot.image |
no | Profile image path (.png, .jpg, .jpeg). Relative paths resolve against the directory containing config.toml. The encoded image must not exceed 12500 characters, roughly a 128x128 avatar. |
bot.db_prefix |
yes | SQLite path prefix. Creates <prefix>_chat.db and <prefix>_agent.db. Under Docker it must point inside /data. |
bot.welcome |
yes | Message posted into each new business chat, sent as the address auto-reply. Multi-line TOML strings are supported. |
roster.group_name |
yes | Name of the roster group, applied when it is created. |
roster.member_role |
no | Role roster members receive in business chats: observer, author, member, moderator, admin or owner. Defaults to owner. |
health.enabled |
no | Set false to switch the monitoring endpoint off. On by default. |
health.host |
no | Interface the endpoint binds. Defaults to 127.0.0.1; 0.0.0.0 under Docker. |
health.port |
no | Port for the endpoint. Defaults to 8080. Setting either key makes a bind failure fatal. |
Changing bot.welcome or bot.image applies on the next start.
The first start logs two links: the business address, for customers, and the roster group link, for people who should answer. Anyone who joins the roster group can add themselves to every incoming chat.
Monitoring
The bot serves GET /health unless health.enabled is false:
| Status | Meaning |
|---|---|
200 {"status":"ok"} |
The core answered a query against the roster group. |
503 {"status":"unavailable"} |
It returned an error, or did not answer within 5 seconds. |
A bot whose messaging servers are unreachable still answers 200.
There is no authentication. Bind it to 127.0.0.1, or to an interface only the
monitoring system can reach. If health.host or health.port is set and the
address cannot be bound, the bot exits; otherwise a busy default port only logs
a warning.
Commands
Available in the roster group.
| Command | Effect |
|---|---|
/dm |
Join the roster. If the bot has no direct contact, it sends a contact request first; membership becomes active once that request is accepted. |
/list |
List active members, members who are no longer reachable, and those pending a contact request. |
/leave |
Leave the roster. Chats already joined are unaffected. |
/help |
Summarise the above. |
Leaving the roster group, or being removed from it, also takes a member off the roster. The bot is the group's only owner, so removing another member requires a client signed in as the bot.
State
All state is in the databases at bot.db_prefix. Roster membership is stored in
each contact's custom_data, and the roster group is found by a marker in the
group's custom_data rather than by name.
Startup reconciles what downtime missed: acceptances that arrived while the bot was stopped, members who left the roster group, and business chats left without their roster members.
Development
source .venv/bin/activate
ruff check && ruff format --check src tests && pyright && pytest tests/ -v
Scope ruff format to src tests. An unscoped run also reformats Python
fenced inside markdown files.
Limitations
- Joining the roster never grants access to earlier conversations, including chats a returning customer reopens.
bot.display_namecannot be changed to a name any contact, group or past customer already holds. The bot logs this and keeps its current name.- Every active member is added to every incoming chat. There is no routing or per-customer selection.
- There is no command to remove someone else from the roster, and
/leavedoes not remove anyone from chats they have already joined.