Skip to content
robdevopsPublic

About

LLM bot for signal built on signal-cli

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

signalbot

A small, privacy-conscious chat bot that connects to a running signal-cli daemon and answers using a model from OpenRouter or a compatible provider.

Features

  • Responds to @mentions, replies to its messages, and DMs from whitelisted contacts
  • Tools: web search, line and pie charts, and your own MCP connectors
  • /schedule a message or command, once or repeating
  • Understands files (PDFs, text and code, Word, Excel and PowerPoint) and the pages you link to, and if the model supports it, images, stickers, voice notes and other audio
  • Typing indicators, reactions, stickers, @mentions and replies
  • In-memory conversation context (Default: 10 messages). Never logged to disk, forgotten after 24 hours, or sooner if disappearing messages requires.

Use cases

  • Your group's brain trust: ask it to explain things, check facts or settle debates, or just the fun of a sassy LLM in your Signal groups
  • Explain or translate any message, photo, document, or website by replying to it and @mentioning the bot
  • Connect MCP tools, data, and charting to your group chat
  • A private assistant in your DMs
  • Use this project as the basis of your own bot project

The messages the bot answers, and the names in them, are sent to the model's API (OpenRouter, unless you choose another). See PRIVACY.md for what happens to your messages.

Install

Prerequisites

Signalbot requires a running signal-cli daemon with your Signal account linked, listening on 127.0.0.1:7583 (signal-cli -o json daemon --tcp 127.0.0.1:7583 --no-receive-stdout).

examples/signal-cli.service runs signal-cli as above. Edit User= and the path in ExecStart, then copy it to /etc/systemd/system/, and run sudo systemctl daemon-reload and sudo systemctl enable --now signal-cli.

Secure your signal account details:

chmod 700 ~/.local/share/signal-cli

Warning

On TCP, anyone with a login or a process on the host can connect to 127.0.0.1:7583 and read or send messages as your bot's Signal account. To harden your security, use signal-cli's Unix socket instead.

Configuration

Get the code and create the config, which is root-only since it holds your API key:

git clone https://github.com/robdevops/signalbot.git
cd signalbot
sudo install -m 660 .env.example /etc/signalbot.env
sudoedit /etc/signalbot.env

To get the bot working with Signal and a model on OpenRouter, set these:

Variable Default Description
SIGNALCLI_SOCKET 127.0.0.1:7583 signal-cli's host:port, or its Unix socket path
MODEL_API_KEY empty Your OpenRouter API key (or see other providers)
MODEL deepseek/deepseek-v4.1-flash-1017 The model, as OpenRouter's provider/model
SIGNAL_DM_USER_IDS empty UUIDs or numbers allowed to DM the bot, separated by commas or whitespace; empty turns DMs off

Every other setting (daily budget, chart colours, history length, MCP connectors, time zone, …) is in Configuration. Then install the bot with systemd or with Docker.

Install as a systemd service

The bot runs as a dynamic user (DynamicUser=yes). Its schedules and budget are kept in /var/lib/private/signalbot, which systemd creates.

  1. Install Go 1.27+:
    curl -fsSLO https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
    sudo rm -rf /usr/local/go
    sudo tar -C /usr/local -xzf go1.27.3.linux-amd64.tar.gz
    
  2. Build and install:
    /usr/local/go/bin/go build -o signalbot
    sudo install -m 755 signalbot /usr/local/bin/signalbot
    sudo cp signalbot.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now signalbot
    
  3. Read the logs:
    journalctl -fu signalbot
    journalctl -u signalbot -p warning   # warnings and errors only
    

Install as a Docker container

compose.yaml runs the bot with Docker Compose, hardened (read-only, no capabilities, memory and process limits) and on the host's network, so it reaches signal-cli on 127.0.0.1. Run it with sudo, because Compose reads /etc/signalbot.env as the calling user and the file is root-only.

Schedules are saved in /data in the container, bind-mounted from the same host directory systemd would use. The container runs as user 65532, which must own it, or the bot exits asking for this:

sudo mkdir -p /var/lib/private/signalbot
sudo chown 65532 /var/lib/private/signalbot

Use one install per host, systemd or Docker: the unit's dynamic user and the container's user 65532 can't share the directory.

Build and start it, from the code's directory:

sudo VERSION=$(git describe --tags --always --dirty) LOG_DRIVER=$(test -S /run/systemd/journal/socket && echo journald || echo local) docker compose up -d --build

Read the logs:

docker logs -f signalbot

Logs go to journald where the host has it, otherwise to Docker's rotating local driver; docker logs reads both. Under journald, journalctl CONTAINER_NAME=signalbot -p err shows warnings and errors.

Build without charts

Charts and their fonts can be left out of the binary (about 0.7 MB of 10 MB) with a build tag: go build -tags nocharts, or for Docker, add TAGS=nocharts after sudo in the build command above. To stop files reaching the model without rebuilding, set FILES=off instead (see Configuration).

Block someone, or other signal-cli chores

While the bot runs, signal-cli's daemon owns the account, so signal-cli itself can't be run alongside it. signalctl sends one request over the same socket the bot uses (SIGNALCLI_SOCKET):

go run ./cmd/signalctl block +15551234567 group:Z3JvdXA=
go run ./cmd/signalctl unblock +15551234567
go run ./cmd/signalctl blocked
go run ./cmd/signalctl call listGroups '{"detailed":true}'

With Docker, it is in the image: docker exec signalbot /signalctl blocked. Under systemd, run it as a user that can reach the socket (anyone on TCP; the signal-cli group for the Unix socket), for example sudo -u signal-cli SIGNALCLI_SOCKET=/run/signal-cli/socket ./signalctl blocked.

A block is Signal's own, as on a phone: signal-cli drops everything from that person or group from then on, including their deletes of messages the bot already holds, and the block syncs to linked devices. call sends any method in signal-cli's JSON-RPC docs. The daemon also sends signalctl's connection the messages it receives; signalctl skips them unread, so it never prints anyone's text.

Test

Add the bot's account to a group and @mention it, reply to one of its messages, or DM it from an allowed sender. Send it /intro or /help to see its introduction.

Docs

About

LLM bot for signal built on signal-cli

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages