Introduction
Welcome to the Meowlnir docs!
This book contains usage and developer documentation for Meowlnir. The book is built using mdBook. The source code is available in the mautrix/docs repo.
Discussion
Matrix room: #meowlnir:maunium.net
Setup
Requirements
- A Matrix homeserver that supports application services (e.g. Synapse). You need access to register an appservice, which usually involves editing the homeserver config file.
If you want to compile Meowlnir manually (which is not required), you'll also need:
- Go 1.23+ (download & installation instructions at https://go.dev/doc/install).
- libolm3 with dev headers and a C/C++ compiler (if you want encryption support).
Installation
You may either compile Meowlnir manually or download a prebuilt executable from the mau.dev CI or GitHub releases. Prebuilt executables are the simplest option, as they don't require having Go nor libolm installed.
Option 1: Downloading a prebuilt executable from CI
- Download the relevant artifacts:
- linux/amd64: https://mau.dev/maunium/meowlnir/-/jobs/artifacts/main/download?job=build%20amd64
- linux/arm64: https://mau.dev/maunium/meowlnir/-/jobs/artifacts/main/download?job=build%20arm64
- linux/arm: https://mau.dev/maunium/meowlnir/-/jobs/artifacts/main/download?job=build%20arm
- or find it yourself on https://mau.dev/maunium/meowlnir/-/pipelines
- Extract the downloaded zip file into a new directory.
Option 2: Downloading a release
- Go to https://github.com/maunium/meowlnir/releases
- Download the binary for the architecture you want and save it in a new directory.
Option 3: Compiling manually
- Clone the repo with
git clone https://github.com/maunium/meowlnir.git
- Enter the directory (
cd meowlnir
) - Run
./build.sh
to fetch Go dependencies and compile ([build.sh
] will simply callgo build
with some additional flags).
- If you want encryption support, make sure you have a C/C++ compiler and the
Olm dev headers (
libolm-dev
on debian-based distros) installed. - If not, use
./build.sh -tags nocrypto
to disable encryption. - As an experimental feature, you can also use
-tags goolm
to use a pure Go reimplementation of libolm. Encryption can be supported without a C compiler or Olm dev headers with this method.
Configuring and running
- Follow the configuration instructions to create a config file and registration.
- Register the registration file with your homeserver (see Registering appservices in the bridge docs for details).
- Run Meowlnir with
./meowlnir
. - Follow the instructions on the Creating bots page to actually initialize your bot, then the Configuring bots page to tell the bot what to do.
Updating
If you compiled manually, pull changes with git pull
and recompile with
./build.sh
.
If you downloaded a prebuilt executable, simply download a new one and replace the old one.
Finally, start Meowlnir again.
Setup with Docker
This page contains instructions for setting up Meowlnir in Docker.
Requirements
- Docker
- A Matrix homeserver that supports application services (e.g. Synapse) You need access to register an appservice, which usually involves editing the homeserver config file.
Setup
Docker images are hosted on dock.mau.dev. Available docker tags are generally
:latest
, :git tag
, :git commit-amd64
and :git commit-arm64
. The latest
and git tag specific docker tags are manifests that contain both amd64 and
arm64 images. :latest
points at the latest commit, not the latest release.
- Create a directory for Meowlnir and cd into it:
mkdir meowlnir && cd meowlnir
. - Pull the docker image with
docker pull dock.mau.dev/maunium/meowlnir:<version>
. Replace<version>
with the version you want to run (e.g.latest
orv0.6.0
). - Follow the configuration instructions to create a config file
and registration.
- To generate the example config using the
-e
flag mentioned in the docs, run the container with/usr/bin/meowlnir -e
as arguments:docker run --rm -v `pwd`:/data:z dock.mau.dev/maunium/meowlnir:<version> /usr/bin/meowlnir -e
- Keep in mind that
localhost
is not the correct address inside Docker (unless usingnetwork=host
mode). Usually you should have Meowlnir and homeserver in the same Docker network, and use the container names as addresses (e.g.http://meowlnir:29339
andhttp://synapse:8008
).
- To generate the example config using the
- Register the registration file with your homeserver (see Registering appservices in the bridge docs for details).
- Run Meowlnir:
Additionally, you should either add Meowlnir to the same Docker network as Synapse withdocker run --restart unless-stopped -v `pwd`:/data:z dock.mau.dev/maunium/meowlnir:<version>
--network=synapsenet
(when both are running in Docker), or expose the correct port with-p 29339:29339
(when the homeserver is outside Docker). - Follow the instructions on the Creating bots page to actually initialize your bot, then the Configuring bots page to tell the bot what to do.
Upgrading
- Pull the new version (setup step 1)
- Start the new version (setup step 6)
Docker compose
- Create a directory for Meowlnir like step #0 in the Docker CLI instructions above.
- Create
docker-compose.yml
that contains something like this:version: "3.7" services: meowlnir: container_name: meowlnir image: dock.mau.dev/maunium/meowlnir:<version> restart: unless-stopped volumes: - .:/data # If you put the service above in the same docker-compose as the homeserver, # ignore the parts below. Otherwise, see below for configuring networking. # If synapse is running outside of docker, you'll need to expose the port. # Note that in most cases you should either run everything inside docker # or everything outside docker, rather than mixing docker things with # non-docker things. #ports: #- "29339:29339" # You'll also probably want this so Meowlnir can reach Synapse directly # using something like `http://host.docker.internal:8008` as the address: #extra_hosts: #- "host.docker.internal:host-gateway" # If synapse is in a different network, then add this container to that network. #networks: #- synapsenet # This is also a part of the networks thing above #networks: # synapsenet: # external: # name: synapsenet
- Follow the rest of the Docker setup, but use compose commands instead of the
raw
docker
commands:docker-compose up -d
to start,docker-compose stop
to stop anddocker-compose pull
to update.
If you want to set it up in an existing docker-compose file instead of a new
dedicated one, simply adjust the volumes
section to mount a subdirectory
instead of the current directory as the data directory:
volumes:
- ./meowlnir:/data
When you put Meowlnir and Synapse in the same docker-compose file, networking
should work out of the box, which means you don't need any of the commented
ports
or networks
things in the example compose file.
Configuring Meowlnir
Like bridges, the example config can be generated with ./meowlnir -e
.
Alternatively, you can find it in the repository in ./config/example-config.yaml.
Most of the config is self-documenting via comments. After filling out the config, refer to the appservice registration section below.
Meowlnir requires its own database (both SQLite and Postgres are supported), and can use read-only access to the Synapse database for optional features and performance improvements.
Notes on Synapse database access
A read-only user can be created with something like this:
CREATE USER meowlnir WITH PASSWORD '...';
GRANT CONNECT ON DATABASE synapse TO meowlnir;
GRANT USAGE ON SCHEMA public TO meowlnir;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO meowlnir;
The current primary reason Meowlnir reads the database directly is to get events to redact more efficiently, and to find soft-failed events (which may not have soft-failed on other servers).
To make finding events more efficient, you may want to add an index:
CREATE INDEX meowlnir_event_sender_idx ON events (room_id, sender);
Appservice registration
After configuring Meowlnir itself, make a registration file, such as this:
# ID and tokens must be exactly the same as in the Meowlnir config.
id: ...
as_token: ...
hs_token: ...
# The URL where the homeserver can reach Meowlnir.
url: http://localhost:29339
# This doesn't matter, just needs to be unique.
sender_localpart: any random string here
# Meowlnir will not handle ratelimits, so this must be false.
rate_limited: false
# Meowlnir uses MSC3202, MSC4190 and MSC4203 for encryption,
# so they must be enabled if you want encryption support.
org.matrix.msc3202: true
io.element.msc4190: true
# push_ephemeral is the old name of receive_ephemeral, as Synapse hasn't stabilized MSC2409 support yet.
# Enabling 2409/receive_ephemeral is required for MSC4203, but is not otherwise used by Meowlnir.
de.sorunome.msc2409.push_ephemeral: true
receive_ephemeral: true
# Add the bots you want here. If you only want one bot, a static regex is enough.
# Multiple bots are supported too and can be dynamically added if you set a non-static regex (e.g. `@moderation_.+:example\.com`)
namespaces:
users:
- regex: '@abuse:example\.com'
exclusive: true
Additionally, you'll need to enable some experimental features in the Synapse config if using encryption:
experimental_features:
# Actually MSC4203, but it was previously a part of MSC2409
msc2409_to_device_messages_enabled: true
# MSC3202 has two parts, both need to be enabled
msc3202_device_masquerading: true
msc3202_transaction_extensions: true
If you use a homeserver without MSC4203/MSC3202 support, or don't want to enable them in Synapse, you can use Meowlnir without them by disabling encryption support entirely in the Meowlnir config.
After you have the registration file, register it with your homeserver like any other appservice. You can refer to the Registering appservices page in bridge docs for details.
Running on a non-Synapse server
While Meowlnir is designed to be used with Synapse, it can be used with other server implementations as well.
If your server doesn't support all the MSCs mentioned in the appservice
registration section, set encryption
-> enable
to false
to disable
encryption entirely. The experimental_features
mentioned above as well as
the unstable prefixed registration fields aren't necessary when encryption
is disabled.
The Synapse database connection can be skipped by leaving type
and uri
blank in the config. Note that unless your homeserver implements MSC4194,
redacting messages from banned users will use a much slower approach of
paginating all messages in the room from the past 24 hours to find messages to
redact.
You can technically point Meowlnir at another server's Synapse database too; it doesn't have to be the database of the server the bot is connected to. If doing that, ensure that the target Synapse is in all the protected rooms so that it receives all events that may need to be redacted in the future.
Reverse proxy configuration
Meowlnir can handle the following endpoints:
/_meowlnir/*
for internal Meowlnir API. Doesn't need to be exposed publicly yet, as there's no web interface.- Synapse needs to be able to reach
/_meowlnir/antispam/*
if using invite blocking.
- Synapse needs to be able to reach
- The standard
/_matrix/app/*
endpoints used by appservices. Only your homeserver needs to be able to reach them. /_matrix/policy/unstable/org.matrix.msc4284/event/{eventID}/check
(or just/_matrix/policy/*
) if using Meowlnir as a policy server (MSC4284).- Reporting endpoints for report interception:
/_matrix/client/v3/rooms/{roomID}/report
/_matrix/client/v3/rooms/{roomID}/report/{eventID}
/_matrix/client/v3/users/{userID}/report
Example configurations
Caddy
matrix-client.example.com {
@reporting {
path /_matrix/client/v3/rooms/*/report/* /_matrix/client/v3/rooms/*/report /_matrix/client/v3/users/*/report
}
handle @reporting {
reverse_proxy http://localhost:29339
}
}
matrix-federation.example.com {
handle /_matrix/policy/* {
reverse_proxy http://localhost:29339
}
}
Blocking invites
To use policy lists for blocking incoming invites, install the synapse-http-antispam module, then configure it with the ID of the management room you want to use plus the antispam API token from the Meowlnir config file:
modules:
- module: synapse_http_antispam.HTTPAntispam
config:
base_url: http://localhost:29339/_meowlnir/antispam/<management room ID>
authorization: <value of antispam.secret>
enabled_callbacks:
- user_may_invite
- user_may_join_room
async:
user_may_join_room: true
The user_may_invite
callback is used to block invites from users who are
banned on any of the policy lists that the management room is subscribed to.
The user_may_join_room
callback is used to track whether non-blocked invites
have been accepted. If auto_reject_invites_token
is set in the config,
Meowlnir will automatically reject pending invites to rooms from banned users.
Even if this callback is not enabled, Meowlnir will still check whether the
invites are pending to avoid rejecting already-accepted invites.
Report interception
Meowlnir can intercept reports from local users for either notifying the admins, or processing commands from the admins.
To use it, you must proxy the report endpoints to Meowlnir (see
Reverse proxy configuration) and define a report_room
in
the Meowlnir config file. Reports from local users sent using the report button
in clients will then send notices to the specified room. The room must be a
management room (defined using the PUT .../management_room/...
endpoint in the
Meowlnir API).
Users who are admins in the management room can also send commands to Meowlnir
via the report feature. Currently, the only existing command is /ban
, which
can be used to send a ban policy event.
To ban a user, report one of their messages and type /ban <list> <reason>
as the report reason. Meowlnir will then find the policy list whose shortcode is
<list>
and send a ban policy with <reason>
as the reason.
Creating bots
You may have noticed that the config file doesn't have anything about the bot username or management rooms. This is because the bots and management rooms can be created dynamically at runtime and are saved in the database. The management secret specified in the config is used to authenticate with the API that can create bots.
Currently existing endpoints:
GET /_meowlnir/v1/bots
- List all botsPUT /_meowlnir/v1/bot/{localpart}
- Create a botPOST /_meowlnir/v1/bot/{localpart}/verify
- Cross-sign a bot's devicePUT /_meowlnir/v1/management_room/{roomID}
- Define a room as a management room
There will be a CLI and/or web UI later, but for now, you can use curl:
export AUTH="Authorization: Bearer $MANAGEMENT_SECRET"
First, create a bot. This example copies matrix.org's admin bot (abuse
as the
username, Administrator
as the displayname, and the same avatar):
curl -H "$AUTH" https://meowlnir.example.com/_meowlnir/v1/bot/abuse -XPUT -d '{"displayname": "Administrator", "avatar_url": "mxc://matrix.org/NZGChxcCXbBvgkCNZTLXlpux"}'
Assuming you didn't have an @abuse user before or if it didn't have encryption, you can have Meowlnir generate cross-signing keys to verify itself. This command will return the recovery key. Make sure to save it!
If you don't have encryption enabled, you can skip this step and jump straight to defining a management room.
curl -H "$AUTH" https://meowlnir.example.com/_meowlnir/v1/bot/abuse/verify -d '{"generate": true}'
Alternatively, if the user already has cross-signing set up, you can provide the recovery key for verification:
curl -H "$AUTH" https://meowlnir.example.com/_meowlnir/v1/bot/abuse/verify -d '{"recovery_key": "EsT* ****..."}'
Finally, you need to define a management room. Create the room normally, get the room ID and run:
curl -H "$AUTH" -X PUT 'https://meowlnir.example.com/_meowlnir/v1/management_room/!randomroomid:example.com' -d '{"bot_username": "abuse"}'
After defining the room, you can invite the bot, and it should accept the invite (you can also invite the bot beforehand if you prefer).
You can define multiple management rooms for the same bot if you want different configurations for different protected rooms. You can also create multiple bots as long as the appservice registration allows it via namespaces.
Configuring bots
After you've created a bot and assigned at least one management room, you'll need to configure the bot in the management room to tell it which rooms to protect and which policy lists to watch. All such configuration is stored in room state events, which is what allows you to have different sets of configs by having multiple management rooms.
In the future, there will be commands for managing all the state events, but for
now you need to send some of them manually (e.g. via /devtools
in Element Web).
Subscribing to policy lists
The fi.mau.meowlnir.watched_lists
state event is used to subscribe to policy
lists. The state key must be empty and the content must have a lists
key,
which is a list of objects. Each object must contain room_id
, shortcode
and
name
, and may specify certain extra flags (documented below).
For example, the event below will apply CME bans and Cat's Active Threats to protected rooms, as well as watch matrix.org's lists without applying them to rooms (i.e. the bot will send messages when the list adds policies, but won't take action based on those).
{
"lists": [
{
"auto_unban": true,
"name": "CME bans",
"room_id": "!fTjMjIzNKEsFlUIiru:neko.dev",
"shortcode": "cme"
},
{
"name": "Cat's Active Threats",
"room_id": "!QJKZNWnsItkUuthamp:feline.support",
"shortcode": "cat"
},
{
"auto_unban": true,
"dont_apply": true,
"name": "matrix.org coc",
"room_id": "!WuBtumawCeOGEieRrp:matrix.org",
"shortcode": "morg-coc"
},
{
"auto_unban": true,
"dont_apply": true,
"name": "matrix.org tos",
"room_id": "!tUPwPPmVTaiKXMiijj:matrix.org",
"shortcode": "morg-tos"
}
]
}
The aliases for the policy lists referenced above are:
- #community-moderation-effort-bl:neko.dev
- #huginn-muminn-active-threats:feline.support
- #matrix-org-coc-bl:matrix.org
- #matrix-org-hs-tos-bl:matrix.org
When you send the event adding a new watched list, Meowlnir will confirm it was successful by sending a message. If you added a list and no message was sent, you probably did something wrong.
To make the bot join a policy list, use the !join <room ID or alias>
command.
Available extra flags:
dont_apply
- Watch the list (send notifications) without taking action based on the policies.dont_apply_acl
- Watch the list and apply all other actions, except for updatingm.room.server_acl
events in protected rooms.auto_unban
- Automatically unban users if the policy that triggered the ban is removed.auto_suspend
- If a policy bans a local user, suspend them using the Synapse admin API automatically. The bot user must be marked as a server admin.dont_notify_on_change
- Don't send management room notifications when policies are added, removed or modified. Useful if you have multiple management rooms and don't want to be spammed in all of them.
Protecting rooms
Protected rooms are listed in the fi.mau.meowlnir.protected_rooms
state event.
The state key must be empty and the event content is simply a rooms
key which
is a list of room IDs.
You can also use the !rooms protect <id or alias>
command instead of sending
the state event manually.
{
"rooms": [
"!randomid:example.com",
"!anotherrandomid:example.com"
]
}
After adding rooms to this list, you can invite the bot to the room, or use the
!join
command.