Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Download the relevant artifacts:
  2. Extract the downloaded zip file into a new directory.

Option 2: Downloading a release

  1. Go to https://github.com/maunium/meowlnir/releases
  2. Download the binary for the architecture you want and save it in a new directory.

Option 3: Compiling manually

  1. Clone the repo with git clone https://github.com/maunium/meowlnir.git
  2. Enter the directory (cd meowlnir)
  3. Run ./build.sh to fetch Go dependencies and compile ([build.sh] will simply call go 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

  1. Follow the configuration instructions to create a config file and registration.
  2. Register the registration file with your homeserver (see Registering appservices in the bridge docs for details).
  3. Run Meowlnir with ./meowlnir.
  4. 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.

  1. Create a directory for Meowlnir and cd into it: mkdir meowlnir && cd meowlnir.
  2. 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 or v0.6.0).
  3. 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 using network=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 and http://synapse:8008).
  4. Register the registration file with your homeserver (see Registering appservices in the bridge docs for details).
  5. Run Meowlnir:
    docker run --restart unless-stopped -v `pwd`:/data:z dock.mau.dev/maunium/meowlnir:<version>
    
    Additionally, you should either add Meowlnir to the same Docker network as Synapse with --network=synapsenet (when both are running in Docker), or expose the correct port with -p 29339:29339 (when the homeserver is outside Docker).
  6. 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

  1. Pull the new version (setup step 1)
  2. Start the new version (setup step 6)

Docker compose

  1. Create a directory for Meowlnir like step #0 in the Docker CLI instructions above.
  2. 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
    
    
  3. 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 and docker-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.
  • 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 bots
  • PUT /_meowlnir/v1/bot/{localpart} - Create a bot
  • POST /_meowlnir/v1/bot/{localpart}/verify - Cross-sign a bot's device
  • PUT /_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:

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 updating m.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.