matrix-cli is a headless and lightweight Matrix client written in Go, designed to operate from the terminal.
Primary Use Case: It acts as an E2EE (End-to-End Encryption) helper tool for external scripts and bots (like router management bots). While unencrypted Matrix rooms can be managed with simple HTTP API requests (e.g., using curl), interacting with encrypted rooms requires complex cryptographic state management. matrix-cli handles all the heavy lifting of E2EE, authentication, and sync, outputting structured JSON that can be easily parsed and consumed by your external tools.
Currently, matrix-cli supports the following operations:
- Authentication: Login to your Matrix account using Password or SSO/OAuth (supports
.well-knowndiscovery for server URLs). - Key Bootstrap: Initialize and manage cross-signing keys for the session (required for E2EE). Can also be used to verify a new device if you have a recovery key.
- Listening: Sync and listen for incoming messages and events in real-time. Events are printed to stdout as JSON for easy parsing by external tools.
- Messaging: Send text messages to specific encrypted or unencrypted rooms.
- Verification: Start an interactive device verification (SAS) flow to support E2EE.
- Rooms Management: List all joined rooms and fetch detailed information about specific rooms (including member counts, power levels, and encryption status) in JSON format.
- Devices: List active devices for your account.
This project relies on the excellent mautrix-go library for all Matrix API interactions and cryptographic operations.
You can download the pre-compiled binary for your architecture (Linux, macOS, Windows) directly from the Releases page. The binaries are packed as .tar.gz (for Linux/macOS) or .zip (for Windows).
Below is an example of how to manually download, extract, and install the Linux AMD64 binary. You can modify the variables for your specific OS/Architecture:
# 1. Set the variables for your target system
REPO="underhax/matrix-cli"
VERSION=$(curl -sSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | head -n 1 | awk -F'"' '{print $4}')
ASSET_NAME="matrix-cli-linux-amd64.tar.gz"
INSTALL_DIR="/usr/local/bin"
# 2. Download the archive
curl -sSL -O "https://github.com/${REPO}/releases/download/${VERSION}/${ASSET_NAME}"
# 3. Extract only the binary from the archive
tar -xzf "${ASSET_NAME}" matrix-cli
# 4. Install the binary (requires sudo if moving to /usr/local/bin)
sudo mv matrix-cli "${INSTALL_DIR}/"
# 5. Clean up
rm "${ASSET_NAME}"It is recommended to place the binary in your system's PATH (such as /usr/local/bin), but you can place it in any directory you prefer. Once installed, refer to the Usage section below to get started.
-
Clone the repository:
git clone https://github.com/underhax/matrix-cli.git cd matrix-cli -
Compile the binary. Important: You must include the
goolmtag to enable the pure-Go implementation of the Olm and Megolm cryptographic ratchets, which are required for E2EE support:go build -tags goolm -o matrix-cli ./cmd/matrix-cli/
matrix-cli operates in different modes. You can run matrix-cli --mode <mode> -h at any time to see detailed instructions.
Usage: matrix-cli --mode auth --server <DOMAIN_OR_URL> [--user <ID>] [--pass <PASSWORD>] [--sso-callback-port <PORT>] [--device <NAME>] [--data-dir <PATH>]
Login to Matrix and save session. Supports both SSO/OAuth and password login.
Examples:
# Auto-discover API URL and use SSO or prompt interactively (recommended):
matrix-cli --mode auth --server 'matrix.org'
# Specify exact HTTPS URL and force password login:
matrix-cli --mode auth --server 'https://synapse.example.com' --user '@bot:example.com' --pass 's3cret'
# Use SSO with a specific callback port:
matrix-cli --mode auth --server 'matrix.example.com' --sso-callback-port 8080
Usage: matrix-cli --mode bootstrap [--new-keys] [--recovery-key <KEY_STRING>] [--data-dir <PATH>]
Initialize cross-signing keys for the current session.
Examples:
# Interactively prompt for recovery key (secure and recommended):
matrix-cli --mode bootstrap
# Generate new keys (may prompt for password depending on UIA):
matrix-cli --mode bootstrap --new-keys
# Load keys explicitly (pass the actual 48-character string, not a file path):
matrix-cli --mode bootstrap --recovery-key 'XXXX-XXXX-XXXX-XXXX'
Usage: matrix-cli --mode send --rooms '<ID>' --message '<TEXT>' [--html | --markdown] [--data-dir <PATH>]
Send a message to one or more rooms. Supports optional HTML or Markdown formatting.
Examples:
# Send a standard text message:
matrix-cli --mode send --rooms '!abc123:matrix.org' --message 'Hello!'
matrix-cli --mode send --rooms '!abc123:matrix.org !def456:matrix.org' --message 'Broadcast!'
# Send a message parsed as Markdown:
matrix-cli --mode send --rooms '!abc123:matrix.org' --markdown --message '**Hello** from *Matrix CLI*!'
# Send a message with raw HTML tags:
matrix-cli --mode send --rooms '!abc123:matrix.org' --html --message '<b>Hello</b> from <i>Matrix CLI</i>!'
Usage: matrix-cli --mode listen [--rooms '<ID1> <ID2>'] [--data-dir <PATH>]
Listen for incoming messages and events. If --rooms is provided, only events from those rooms are processed.
Examples:
matrix-cli --mode listen
matrix-cli --mode listen --rooms '!abc123:matrix.org !def456:matrix.org'
matrix-cli --mode listen 2>/dev/null
Usage: matrix-cli --mode rooms [--verbose] [--data-dir <PATH>]
List joined rooms. Use --verbose to fetch name, topic, and alias for each room.
Examples:
matrix-cli --mode rooms
matrix-cli --mode rooms --verbose
Usage: matrix-cli --mode room-info --rooms '<ID>' [--data-dir <PATH>]
Get detailed info for specific room(s).
Examples:
matrix-cli --mode room-info --rooms '!abc123:matrix.org'
matrix-cli --mode room-info --rooms '!abc123:matrix.org !abc123defg456'
Usage: matrix-cli --mode verify [--user <@user:example.com>] [--data-dir <PATH>]
Start an interactive device verification (SAS) flow.
Examples:
# Wait for incoming verification requests:
matrix-cli --mode verify
# Initiate verification with another user (or your own devices):
matrix-cli --mode verify --user '@bob:example.com'
Usage: matrix-cli --mode logout [--data-dir <PATH>]
Logout from the homeserver and delete the local session and database.
Examples:
matrix-cli --mode logout
All commands support the --data-dir flag to specify where the session and database files are stored. By default, this points to your OS's configuration directory (e.g., ~/.config/matrix-cli).
π¦ Docker Deployment & SSH Gateway (Click to expand)
A minimal, hardened Docker container is available via GHCR (ghcr.io/underhax/matrix-cli). It runs matrix-cli behind an SSH reverse tunnel, acting as a secure Matrix protocol gateway for remote clients (e.g. routers or IoT devices).
[Remote client]
|
| SSH reverse tunnel (-R 2222:localhost:2222)
v
[Host: 127.0.0.1:2222]
|
| Docker port mapping
v
[matrix-cli container :2222]
|
| sshd β PubkeyAuthentication only, UsePAM=no
v
[matrix-cli interactive session / scripts]
The container does not bind to any public interface. All external access is funnelled exclusively through the authenticated SSH tunnel established by the remote client.
| Control | Status |
|---|---|
| Runs as UID 10001 (non-root) | β |
no-new-privileges |
β |
| All Linux capabilities dropped | β |
| Read-only root filesystem | β |
/run and /tmp as noexec/nosuid tmpfs |
β |
PAM disabled (UsePAM=no) |
β |
| SSH password authentication disabled | β |
| SSH root login disabled | β |
SSH MaxAuthTries 3, LoginGraceTime 20 |
β |
| SSH X11 forwarding, PermitTunnel, GatewayPorts disabled | β |
Port bound to 127.0.0.1 only |
β |
Multi-arch binary (amd64 / arm64) |
β |
Note on
UsePAM=no: sshd invokes the PAM helperunix_chkpwd(a setuid binary) even when password authentication is disabled. This conflicts withno-new-privileges:true. Since PAM provides no value in a pubkey-only, single-user container, it is explicitly disabled, which resolves the conflict without sacrificing any meaningful security control.
To make installation simple, set your preferred base directory as a variable and run the initial setup commands on the Docker host:
# 1. Define your base directory (change this path to your preference)
export BASE_DIR="/home/user/matrix-cli"
# 2. Create directories and download the compose file
mkdir -p "${BASE_DIR}/bot_data" "${BASE_DIR}/host_keys"
curl -sSL -o "${BASE_DIR}/docker-compose.yaml" https://raw.githubusercontent.com/underhax/matrix-cli/main/docker/docker-compose.yaml
# 3. Set strict ownership for the container (UID/GID 10001)
# All read-write volume mount points on the host must be owned by UID 10001,
# or sshd's StrictModes check will refuse to start.
sudo chown -R 10001:10001 "${BASE_DIR}/bot_data" "${BASE_DIR}/host_keys"Copy the contents of the public SSH key from your remote client (e.g., your router or IoT device). Then, back on the Docker host, open the authorized_keys file:
nano "${BASE_DIR}/authorized_keys"Paste the public key into the file, save, and exit. Then secure the file permissions:
sudo chown 10001:10001 "${BASE_DIR}/authorized_keys"
sudo chmod 0600 "${BASE_DIR}/authorized_keys"Now you can start the gateway service in the background:
docker compose -f "${BASE_DIR}/docker-compose.yaml" up -dOn first run, matrix-cli requires interactive login to create its session database. Start a shell inside the container:
# Before running this command:
# 1. Create a dedicated Matrix account for the bot on your homeserver.
# Do NOT use your personal account β the bot account will be used
# for automated messaging and device verification.
# Assuming your main VPS SSH port is 2223, and the Docker SSH port is 2222:
# Connect to the container and create the tunnel in one step:
ssh -i ~/.ssh/id_docker_key -o ProxyCommand="ssh -i ~/.ssh/id_server_key -W %h:%p -p 2223 root@remote_ip" -p 2222 -L 8080:127.0.0.1:8080 bot@127.0.0.1Once the secure SSH session is established, refer to the Usage section above to:
- Safely authenticate your new bot account.
- Setup E2EE cross-signing by either:
- Generating new keys (
bootstrap) β Use this only if this is a brand new account with no prior devices or keys. - Importing an existing recovery key (
bootstrap) β The recommended method if the bot already has an established account. - Performing interactive device verification with emojis (
verify) β Alternative method to verify your own devices (if you have another session open) or to verify other users' accounts.
- Generating new keys (
After authentication and verification are complete, exit the shell (exit).
Once the session is initialised and verification is complete, restart the container in its normal operating mode β without an interactive shell:
docker compose -f "${BASE_DIR}/docker-compose.yaml" down
docker compose -f "${BASE_DIR}/docker-compose.yaml" up -dThe container will now run as a persistent background service, ready to accept incoming tunnel connections from the remote client.
| Host path | Container path | Mode | Ownership requirement |
|---|---|---|---|
./authorized_keys |
/home/bot/.ssh/authorized_keys |
ro |
chown 10001:10001, chmod 0600 |
./bot_data/ |
/home/bot/.config/matrix-cli |
rw |
chown -R 10001:10001 |
./host_keys/ |
/home/bot/keys |
rw |
chown -R 10001:10001 |
On first startup, the entrypoint generates an SSH host key and persists it to the host at ${BASE_DIR}/host_keys/ssh_host_ed25519_key. This prevents host key churn across container restarts, which would otherwise produce REMOTE HOST IDENTIFICATION HAS CHANGED warnings on the connecting client.
To rotate the host key intentionally:
docker compose -f "${BASE_DIR}/docker-compose.yaml" down
sudo rm "${BASE_DIR}/host_keys/ssh_host_ed25519_key"
docker compose -f "${BASE_DIR}/docker-compose.yaml" up -d