Files
matrix-docker-ansible-deploy/roles/custom/matrix-bot-meowlnir/templates/bin/meowlnir-create-management-room.j2
T
Slavi PantaleevandClaude Opus 5 f9222dc70c Add support for Meowlnir
Meowlnir (https://github.com/maunium/meowlnir) is a Matrix moderation
bot which speaks the same policy-list protocol as Mjolnir and Draupnir,
but runs as an appservice and can override individual policies coming
from ban lists you do not control.

Bots and their management rooms live only in Meowlnir's own database —
nothing in its configuration file can declare one — so the role
provisions them through the management API from a declarative roster
(matrix_bot_meowlnir_bots_custom), applied under the
ensure-matrix-users-created tag. Management rooms may be declared or
created for you; bots and rooms no longer declared get pruned.

Wrapper scripts for driving the management API by hand are installed
to /matrix/meowlnir/bin.

Meowlnir re-runs its configuration upgrader in memory on every start,
so a literal `generate` value yields a new secret per restart. All
secrets are therefore rendered explicitly, validation rejects
`generate`, and the configuration directory is mounted read-only.

Draupnir and Meowlnir both want synapse-http-antispam, which the
playbook wires up to a single consumer. The wiring prefers Draupnir,
and both roles fail the run when each claims it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 06:05:30 +03:00

91 lines
3.2 KiB
Django/Jinja
Executable File

#!/bin/sh
# Creates a management room for a Meowlnir bot, with the given users able to command the bot there.
#
# The room is created by the bot itself, impersonated through the appservice token, so no human account's credentials are needed.
#
# The `trusted_private_chat` preset is what gives the invited users their standing, and it does the right thing on both old and new room versions: on rooms supporting MSC4289 every invitee becomes an additional creator, and on older ones each is given power level 100.
# Either way there is nothing for us to adjust afterwards.
#
# Usage: meowlnir-create-management-room <bot_localpart> <initial_manager_mxid>...
#
# Prints the created room's ID on success.
set -eu
CONFIG_FILE='{{ matrix_bot_meowlnir_config_path }}/config.yaml'
CONTAINER_NAME='matrix-bot-meowlnir'
HOMESERVER_ADDRESS='{{ matrix_bot_meowlnir_config_homeserver_address }}'
HOMESERVER_DOMAIN='{{ matrix_bot_meowlnir_config_homeserver_domain }}'
ROOM_NAME='{{ matrix_bot_meowlnir_management_room_name }}'
ROOM_TOPIC='{{ matrix_bot_meowlnir_management_room_topic | trim }}'
ENCRYPTED='{{ 'true' if matrix_bot_meowlnir_config_encryption_enable else 'false' }}'
if [ $# -lt 2 ]; then
echo "Usage: $(basename "$0") <bot_localpart> <initial_manager_mxid>..." >&2
exit 2
fi
bot_localpart="$1"
shift
bot_mxid="@$bot_localpart:$HOMESERVER_DOMAIN"
as_token="$(awk '$1 == "as_token:" { print $2; exit }' "$CONFIG_FILE" | sed 's/^"//; s/"$//')"
if [ -z "$as_token" ]; then
echo "Could not read as_token from $CONFIG_FILE" >&2
exit 1
fi
urlencode() {
printf '%s' "$1" | sed 's/%/%25/g; s/!/%21/g; s/:/%3A/g; s/@/%40/g; s/\$/%24/g; s/\//%2F/g'
}
jq_run() {
{{ devture_systemd_docker_base_host_command_docker }} exec -i "$CONTAINER_NAME" jq "$@"
}
if [ "$ENCRYPTED" = 'true' ]; then
initial_state='[{"type": "m.room.encryption", "state_key": "", "content": {"algorithm": "m.megolm.v1.aes-sha2"}}]'
else
initial_state='[]'
fi
# Matrix user IDs cannot contain newlines, so splitting on them is safe here.
invitees="$(printf '%s\n' "$@" | jq_run -R -s 'split("\n") | map(select(length > 0))')"
create_body="$(jq_run -n \
--arg name "$ROOM_NAME" \
--arg topic "$ROOM_TOPIC" \
--argjson invitees "$invitees" \
--argjson initial_state "$initial_state" \
'{preset: "trusted_private_chat", name: $name, topic: $topic, invite: $invitees, initial_state: $initial_state}')"
user_id_param="$(urlencode "$bot_mxid")"
# Runs curl inside the container, because the homeserver is only reachable over the container network.
# Prints the body, with the HTTP status code on the final line.
response="$({{ devture_systemd_docker_base_host_command_docker }} exec "$CONTAINER_NAME" \
curl -sS -X POST \
-H "Authorization: Bearer $as_token" \
-H 'Content-Type: application/json' \
-d "$create_body" \
-w '\n%{http_code}' \
"$HOMESERVER_ADDRESS/_matrix/client/v3/createRoom?user_id=$user_id_param")"
status="$(printf '%s\n' "$response" | tail -n 1)"
if [ "$status" != '200' ]; then
echo "Creating the management room failed with HTTP $status:" >&2
printf '%s\n' "$response" | sed '$d' >&2
exit 1
fi
room_id="$(printf '%s\n' "$response" | sed '$d' | jq_run -r '.room_id')"
if [ -z "$room_id" ] || [ "$room_id" = 'null' ]; then
echo 'The homeserver did not return a room ID' >&2
exit 1
fi
printf '%s\n' "$room_id"