A self-contained deployment tool for Matrix bots on Linux using Ansible and systemd user services.
Most Ansible collections are libraries: roles and modules that your playbooks import. This collection is different — it is a deployable application you run directly from its own directory.
It ships with:
- Its own
ansible.cfg— no system-wide Ansible config needed - Its own inventory — a named host registry that works on any Linux machine
manage-bot.sh— a dynamic playbook generator that eliminates the need to write playbooks
You clone it, configure two files, and run one command. It works on any recent Linux distribution with Python 3.8+, Ansible, and systemd. No Galaxy installation, no external orchestrator, no shared infrastructure required.
git clone https://github.com/jackaltx/solti-matrix-bots.git
cd solti-matrix-bots
cp inventory/group_vars/all.yml.example inventory/group_vars/all.yml
# edit all.yml and ~/.secrets/LabMatrix
source ~/.secrets/LabMatrix
./manage-bot.sh brain2-bot prepare
./manage-bot.sh brain2-bot deployThis pattern is borrowed from solti-containers, which applies the same approach to Podman quadlet services.
Event validation bot. Joins a room and logs all Matrix events. Useful for verifying infrastructure connectivity and room state. No AI, no user interaction.
- Dependencies:
matrix-nio - Secret:
MATRIX_WATCHER_TOKEN
AI analysis assistant. Responds to @solti-claude-code mentions with file reads, code
search, and safe bash execution inside a configurable working directory. Read-only by design.
Smart model selection: Haiku 4.5 for simple tasks, Sonnet 4.5 for complex analysis.
- Dependencies:
matrix-nio,anthropic - Secrets:
MATRIX_SOLTI_CLAUDE_CODE_TOKEN,ANTHROPIC_API_KEY
Second brain capture bot. Responds to @solti-brain2 mentions, classifies each message
via Claude API (people / projects / ideas / admin / unclassified), and stores the result
in MongoDB with a replay log for classifier tuning.
Confidence ≥ 60% → stored in target collection, bot replies with summary. Confidence < 60% → stored as unclassified, bot asks a follow-up question.
Slash commands: /help /status /cost
- Dependencies:
matrix-nio,anthropic,pymongo - Secrets:
MATRIX_SOLTI_BRAIN2_TOKEN,ANTHROPIC_API_KEY,BRAIN2_MONGODB_URI - Requires: MongoDB running and reachable before deploy
inventory/group_vars/all.yml is required — the bots will not deploy without it.
cp inventory/group_vars/all.yml.example inventory/group_vars/all.yml
nano inventory/group_vars/all.ymlMinimum required content:
domain: "example.com"
matrix_homeserver_url: "https://matrix.{{ domain }}"
matrix_working_dir_default: "{{ ansible_facts['env']['HOME'] }}/your/working/directory"
matrix_watcher_room_default: "#your-room:{{ domain }}"
claude_code_bot_room_default: "#your-room:{{ domain }}"
brain2_bot_room_default: "#SecondBrain:{{ domain }}"
matrix_allowed_users: "@user1:{{ domain }},@user2:{{ domain }}"matrix_allowed_users is a comma-separated list of Matrix user IDs permitted to interact
with the bots. Users not in this list receive an unauthorized error.
nano ~/.secrets/LabMatrix
chmod 600 ~/.secrets/LabMatrix# ~/.secrets/LabMatrix — never commit, chmod 600
# Matrix bot tokens (from Synapse admin API or your matrix_config playbook)
export MATRIX_WATCHER_TOKEN="syt_YOUR_TOKEN_HERE"
export MATRIX_SOLTI_CLAUDE_CODE_TOKEN="syt_YOUR_TOKEN_HERE"
export MATRIX_SOLTI_BRAIN2_TOKEN="syt_YOUR_TOKEN_HERE"
# Anthropic API key (https://console.anthropic.com/settings/keys)
export ANTHROPIC_API_KEY="sk-ant-api03-YOUR_KEY_HERE"
# brain2-bot: MongoDB URI with credentials
export BRAIN2_MONGODB_URI="mongodb://user:password@localhost:27017"source ~/.secrets/LabMatrix
# One-time environment setup (creates venv, directories)
./manage-bot.sh brain2-bot prepare
# Deploy and start the service
./manage-bot.sh brain2-bot deploy
# Check health
./manage-bot.sh brain2-bot verify./manage-bot.sh matrix-watcher prepare
./manage-bot.sh matrix-watcher deploy
./manage-bot.sh matrix-watcher verify
./manage-bot.sh matrix-watcher remove
./manage-bot.sh claude-code-bot deploy
./manage-bot.sh brain2-bot deploy
# Remove and delete data
DELETE_DATA=true ./manage-bot.sh brain2-bot remove# Deploy to a named host from your inventory
./manage-bot.sh -h myserver brain2-bot deploy
# Custom inventory file
./manage-bot.sh -i inventory/remote.yml -h myserver brain2-bot deploy
# Skip confirmation prompts
./manage-bot.sh -y -h myserver brain2-bot deploy# Override any config at run time
MATRIX_HOMESERVER_URL="https://alt.example.com" \
./manage-bot.sh brain2-bot deploy
BRAIN2_MONGODB_URI="mongodb://user:pass@dbhost:27017" \
./manage-bot.sh brain2-bot deploysystemctl --user status brain2-bot.service
journalctl --user -u brain2-bot -f
systemctl --user list-units 'matrix-*' --allsolti-matrix-bots/
├── ansible.cfg # Self-contained Ansible config
├── manage-bot.sh # Dynamic playbook generator (entry point)
├── galaxy.yml # Collection metadata
├── inventory/
│ ├── localhost.yml # Named host registry (firefly = localhost)
│ └── group_vars/
│ └── all.yml.example # Configuration template (copy to all.yml)
└── roles/
├── _bot_base/ # Shared infrastructure for all bots
│ ├── tasks/
│ │ ├── prepare.yml # Venv setup, directories
│ │ ├── present.yml # Service deployment
│ │ ├── cleanup.yml # Service removal
│ │ └── verify.yml # Health check
│ └── templates/
│ └── bot.service.j2 # Systemd user service template
├── matrix_watcher/
├── claude_code_bot/
└── brain2_bot/
manage-bot.sh translates a simple CLI verb into a complete Ansible run:
./manage-bot.sh brain2-bot deploy
↓
generates tmp/manage-brain2-bot-deploy-<pid>.yml
↓
ansible-playbook -i inventory/localhost.yml tmp/...yml
↓
brain2_bot role → _bot_base/tasks/present.yml → systemd service
No playbooks to write. No Galaxy namespace to remember. Clone, configure, run.
prepare → present → verify → absent
↓ ↓ ↓ ↓
setup deploy health remove
All operations are idempotent. Running deploy twice is safe.
Each bot role defines a bot_properties dict that is the contract with _bot_base:
bot_properties:
root: "brain2-bot"
name: "brain2-bot.service"
script_name: "brain2-bot.py"
data_dir: "{{ real_user_dir }}/matrix-bots"
bot_dir: "{{ real_user_dir }}/matrix-bots/brain2-bot"
venv_dir: "{{ real_user_dir }}/matrix-bots/venv" # shared
requirements: ["matrix-nio>=0.25.2", "anthropic>=0.96.0", "pymongo>=4.0"]
environment:
MATRIX_HOMESERVER_URL: "{{ matrix_homeserver_url }}"
MATRIX_ALLOWED_USERS: "{{ matrix_allowed_users | default('') }}"
secrets:
- MATRIX_SOLTI_BRAIN2_TOKEN
- ANTHROPIC_API_KEY
- BRAIN2_MONGODB_URI_bot_base reads this dict and handles venv creation, script deployment, service
rendering, and systemd enable/start — the same way for every bot.
| What | Where | Status |
|---|---|---|
| Collection source | This repo | Public ✓ |
| Generic inventory | inventory/localhost.yml |
Public ✓ |
| Site config | inventory/group_vars/all.yml |
Gitignored |
| Tokens / API keys / DB URIs | ~/.secrets/LabMatrix |
Never in repo |
| Allowed user list | matrix_allowed_users in all.yml |
Gitignored |
Secrets are baked into the systemd service file at deploy time via lookup('env', name).
Source ~/.secrets/LabMatrix before running manage-bot.sh or tokens render as empty strings.
Secret rendered as empty string. Source your secrets file before deploying:
source ~/.secrets/LabMatrix && ./manage-bot.sh brain2-bot deployinventory/group_vars/all.yml doesn't exist or is missing required keys.
cp inventory/group_vars/all.yml.example inventory/group_vars/all.yml
nano inventory/group_vars/all.ymlEnsure the remote has Python 3.8+, a systemd user session, and ~/.secrets/LabMatrix.
- Create
roles/my_bot/defaults/main.yml— definebot_properties - Create
roles/my_bot/tasks/main.yml— state-driven delegation to_bot_base - Add bot script to
roles/my_bot/files/ - Add to
BOT_MAPandSUPPORTED_BOTSinmanage-bot.sh - Add
my_bot_svcgroup toinventory/localhost.yml
See roles/brain2_bot/ as the reference implementation.
- solti-containers — same pattern for Podman quadlet services
- solti-matrix-mgr — Matrix Synapse admin utilities
- solti-conductor — orchestrator template
GPL-3.0-or-later
JackalTX — github.com/jackaltx/solti-matrix-bots