mirror of
https://github.com/spantaleev/matrix-docker-ansible-deploy.git
synced 2026-09-23 00:00:10 +00:00
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bed7bddf4a
commit
f9222dc70c
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
# Talks to Meowlnir's management API.
|
||||
#
|
||||
# The API is not published outside the container network, so requests are made from inside the container, which ships with curl.
|
||||
# The management secret is read out of the live configuration file, so that it lives in exactly one place.
|
||||
#
|
||||
# Usage: meowlnir-api <METHOD> <PATH> [JSON_BODY]
|
||||
# Example: meowlnir-api GET /_meowlnir/v1/bots
|
||||
#
|
||||
# Prints the response body, followed by the HTTP status code on its own final line.
|
||||
|
||||
set -eu
|
||||
|
||||
CONFIG_FILE='{{ matrix_bot_meowlnir_config_path }}/config.yaml'
|
||||
CONTAINER_NAME='matrix-bot-meowlnir'
|
||||
API_BASE='http://localhost:{{ matrix_bot_meowlnir_config_meowlnir_port }}'
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $(basename "$0") <METHOD> <PATH> [JSON_BODY]" >&2
|
||||
echo "Example: $(basename "$0") GET /_meowlnir/v1/bots" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
method="$1"
|
||||
api_path="$2"
|
||||
body="${3:-}"
|
||||
|
||||
# The configuration file is generated by Ansible, so its layout is predictable.
|
||||
secret="$(awk '$1 == "management_secret:" { print $2; exit }' "$CONFIG_FILE" | sed 's/^"//; s/"$//')"
|
||||
|
||||
if [ -z "$secret" ]; then
|
||||
echo "Could not read management_secret from $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$secret" = 'disable' ]; then
|
||||
echo "Meowlnir's management API is disabled (management_secret is set to 'disable')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$body" ]; then
|
||||
exec {{ devture_systemd_docker_base_host_command_docker }} exec "$CONTAINER_NAME" \
|
||||
curl -sS -X "$method" \
|
||||
-H "Authorization: Bearer $secret" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$body" \
|
||||
-w '\n%{http_code}' \
|
||||
"$API_BASE$api_path"
|
||||
fi
|
||||
|
||||
exec {{ devture_systemd_docker_base_host_command_docker }} exec "$CONTAINER_NAME" \
|
||||
curl -sS -X "$method" \
|
||||
-H "Authorization: Bearer $secret" \
|
||||
-w '\n%{http_code}' \
|
||||
"$API_BASE$api_path"
|
||||
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Shows the bots Meowlnir knows about, together with their management rooms, protected rooms and watched policy lists.
|
||||
#
|
||||
# Usage: meowlnir-bots
|
||||
|
||||
set -eu
|
||||
|
||||
BIN_PATH='{{ matrix_bot_meowlnir_bin_path }}'
|
||||
CONTAINER_NAME='matrix-bot-meowlnir'
|
||||
|
||||
response="$("$BIN_PATH/meowlnir-api" GET /_meowlnir/v1/bots)"
|
||||
status="$(printf '%s\n' "$response" | tail -n 1)"
|
||||
body="$(printf '%s\n' "$response" | sed '$d')"
|
||||
|
||||
if [ "$status" != '200' ]; then
|
||||
echo "Meowlnir answered with HTTP $status:" >&2
|
||||
echo "$body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$body" | {{ devture_systemd_docker_base_host_command_docker }} exec -i "$CONTAINER_NAME" jq .
|
||||
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/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"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
@@ -0,0 +1,84 @@
|
||||
{#
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#}
|
||||
{#
|
||||
Note: this template is rendered, parsed as YAML, merged with
|
||||
`matrix_bot_meowlnir_configuration_extension`, and dumped again.
|
||||
Comments written here therefore do not reach the final configuration file.
|
||||
|
||||
Secrets must always be rendered explicitly. Meowlnir re-runs its configuration
|
||||
upgrader in memory on every start, so a literal `generate` value would produce a
|
||||
brand new secret on each restart.
|
||||
#}
|
||||
homeserver:
|
||||
address: {{ matrix_bot_meowlnir_config_homeserver_address | to_json }}
|
||||
domain: {{ matrix_bot_meowlnir_config_homeserver_domain | to_json }}
|
||||
|
||||
meowlnir:
|
||||
id: {{ matrix_bot_meowlnir_appservice_id | to_json }}
|
||||
as_token: {{ matrix_bot_meowlnir_appservice_token | to_json }}
|
||||
hs_token: {{ matrix_bot_meowlnir_homeserver_token | to_json }}
|
||||
|
||||
address: {{ matrix_bot_meowlnir_appservice_url | to_json }}
|
||||
hostname: {{ matrix_bot_meowlnir_config_meowlnir_hostname | to_json }}
|
||||
port: {{ matrix_bot_meowlnir_config_meowlnir_port | int }}
|
||||
|
||||
management_secret: {{ matrix_bot_meowlnir_config_meowlnir_management_secret | to_json }}
|
||||
data_secret: {{ matrix_bot_meowlnir_config_meowlnir_data_secret | to_json }}
|
||||
federation_auth: {{ matrix_bot_meowlnir_config_meowlnir_federation_auth | to_json }}
|
||||
dry_run: {{ matrix_bot_meowlnir_config_meowlnir_dry_run | to_json }}
|
||||
untrusted: {{ matrix_bot_meowlnir_config_meowlnir_untrusted | to_json }}
|
||||
|
||||
report_room: {{ matrix_bot_meowlnir_config_meowlnir_report_room | to_json if matrix_bot_meowlnir_config_meowlnir_report_room else 'null' }}
|
||||
room_ban_room: {{ matrix_bot_meowlnir_config_meowlnir_room_ban_room | to_json if matrix_bot_meowlnir_config_meowlnir_room_ban_room else 'null' }}
|
||||
load_all_room_hashes: {{ matrix_bot_meowlnir_config_meowlnir_load_all_room_hashes | to_json }}
|
||||
|
||||
hacky_rule_filter: {{ matrix_bot_meowlnir_config_meowlnir_hacky_rule_filter | to_json }}
|
||||
hacky_redact_patterns: {{ matrix_bot_meowlnir_config_meowlnir_hacky_redact_patterns | to_json }}
|
||||
|
||||
admin_tokens: {{ matrix_bot_meowlnir_config_meowlnir_admin_tokens | to_json }}
|
||||
|
||||
meowlnir4all:
|
||||
admin_room: {{ matrix_bot_meowlnir_config_meowlnir4all_admin_room | to_json if matrix_bot_meowlnir_config_meowlnir4all_admin_room else 'null' }}
|
||||
localpart_template: {{ matrix_bot_meowlnir_config_meowlnir4all_localpart_template | to_json }}
|
||||
displayname: {{ matrix_bot_meowlnir_config_meowlnir4all_displayname | to_json if matrix_bot_meowlnir_config_meowlnir4all_displayname else 'null' }}
|
||||
avatar_url: {{ matrix_bot_meowlnir_config_meowlnir4all_avatar_url | to_json if matrix_bot_meowlnir_config_meowlnir4all_avatar_url else 'null' }}
|
||||
room_name: {{ matrix_bot_meowlnir_config_meowlnir4all_room_name | to_json }}
|
||||
default_watched_lists: {{ matrix_bot_meowlnir_config_meowlnir4all_default_watched_lists | to_json }}
|
||||
|
||||
antispam:
|
||||
secret: {{ matrix_bot_meowlnir_config_antispam_secret | to_json }}
|
||||
filter_local_invites: {{ matrix_bot_meowlnir_config_antispam_filter_local_invites | to_json }}
|
||||
auto_reject_invites_token: {{ matrix_bot_meowlnir_config_antispam_auto_reject_invites_token | to_json if matrix_bot_meowlnir_config_antispam_auto_reject_invites_token else 'null' }}
|
||||
notify_management_room: {{ matrix_bot_meowlnir_config_antispam_notify_management_room | to_json }}
|
||||
block_invites_to: {{ matrix_bot_meowlnir_config_antispam_block_invites_to | to_json }}
|
||||
|
||||
policy_server:
|
||||
always_redact: {{ matrix_bot_meowlnir_config_policy_server_always_redact | to_json }}
|
||||
signing_key: {{ matrix_bot_meowlnir_config_policy_server_signing_key | to_json }}
|
||||
|
||||
encryption:
|
||||
enable: {{ matrix_bot_meowlnir_config_encryption_enable | to_json }}
|
||||
pickle_key: {{ matrix_bot_meowlnir_config_encryption_pickle_key | to_json }}
|
||||
|
||||
database:
|
||||
type: {{ matrix_bot_meowlnir_database_engine | to_json }}
|
||||
uri: {{ matrix_bot_meowlnir_database_connection_string | to_json }}
|
||||
max_open_conns: {{ matrix_bot_meowlnir_config_database_max_open_conns | int }}
|
||||
max_idle_conns: {{ matrix_bot_meowlnir_config_database_max_idle_conns | int }}
|
||||
max_conn_idle_time: {{ matrix_bot_meowlnir_config_database_max_conn_idle_time | to_json if matrix_bot_meowlnir_config_database_max_conn_idle_time else 'null' }}
|
||||
max_conn_lifetime: {{ matrix_bot_meowlnir_config_database_max_conn_lifetime | to_json if matrix_bot_meowlnir_config_database_max_conn_lifetime else 'null' }}
|
||||
|
||||
synapse_db:
|
||||
type: postgres
|
||||
uri: {{ matrix_bot_meowlnir_synapse_database_uri | to_json }}
|
||||
max_open_conns: {{ matrix_bot_meowlnir_config_synapse_db_max_open_conns | int }}
|
||||
max_idle_conns: {{ matrix_bot_meowlnir_config_synapse_db_max_idle_conns | int }}
|
||||
max_conn_idle_time: {{ matrix_bot_meowlnir_config_synapse_db_max_conn_idle_time | to_json if matrix_bot_meowlnir_config_synapse_db_max_conn_idle_time else 'null' }}
|
||||
max_conn_lifetime: {{ matrix_bot_meowlnir_config_synapse_db_max_conn_lifetime | to_json if matrix_bot_meowlnir_config_synapse_db_max_conn_lifetime else 'null' }}
|
||||
|
||||
logging:
|
||||
min_level: {{ matrix_bot_meowlnir_config_logging_min_level | to_json }}
|
||||
writers: {{ matrix_bot_meowlnir_config_logging_writers | to_json }}
|
||||
@@ -0,0 +1,78 @@
|
||||
{#
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#}
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_traefik_enabled %}
|
||||
traefik.enable=true
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_traefik_docker_network %}
|
||||
traefik.docker.network={{ matrix_bot_meowlnir_container_labels_traefik_docker_network }}
|
||||
{% endif %}
|
||||
|
||||
traefik.http.services.matrix-bot-meowlnir.loadbalancer.server.port={{ matrix_bot_meowlnir_config_meowlnir_port }}
|
||||
|
||||
{% if matrix_bot_meowlnir_config_reporting_enabled %}
|
||||
############################################################
|
||||
# #
|
||||
# Reports (/_matrix/client/../rooms/../report) #
|
||||
# #
|
||||
############################################################
|
||||
|
||||
{# Meowlnir serves these paths verbatim and applies CORS headers itself, #}
|
||||
{# so no path-rewriting or CORS middleware is necessary here. #}
|
||||
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.rule={{ matrix_bot_meowlnir_container_labels_reporting_traefik_rule }}
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_reporting_traefik_priority | int > 0 %}
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.priority={{ matrix_bot_meowlnir_container_labels_reporting_traefik_priority }}
|
||||
{% endif %}
|
||||
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.service=matrix-bot-meowlnir
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.entrypoints={{ matrix_bot_meowlnir_container_labels_reporting_traefik_entrypoints }}
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.tls={{ matrix_bot_meowlnir_container_labels_reporting_traefik_tls | to_json }}
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_reporting_traefik_tls %}
|
||||
traefik.http.routers.matrix-bot-meowlnir-reporting.tls.certResolver={{ matrix_bot_meowlnir_container_labels_reporting_traefik_tls_certResolver }}
|
||||
{% endif %}
|
||||
|
||||
############################################################
|
||||
# #
|
||||
# /Reports (/_matrix/client/../rooms/../report) #
|
||||
# #
|
||||
############################################################
|
||||
{% endif %}
|
||||
|
||||
{% if matrix_bot_meowlnir_policy_server_enabled %}
|
||||
############################################################
|
||||
# #
|
||||
# Policy server (/_matrix/policy) #
|
||||
# #
|
||||
############################################################
|
||||
|
||||
{# Served on the federation endpoint, so that other servers in a room can reach it. #}
|
||||
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.rule={{ matrix_bot_meowlnir_container_labels_policy_server_traefik_rule }}
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_policy_server_traefik_priority | int > 0 %}
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.priority={{ matrix_bot_meowlnir_container_labels_policy_server_traefik_priority }}
|
||||
{% endif %}
|
||||
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.service=matrix-bot-meowlnir
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.entrypoints={{ matrix_bot_meowlnir_container_labels_policy_server_traefik_entrypoints }}
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.tls={{ matrix_bot_meowlnir_container_labels_policy_server_traefik_tls | to_json }}
|
||||
|
||||
{% if matrix_bot_meowlnir_container_labels_policy_server_traefik_tls %}
|
||||
traefik.http.routers.matrix-bot-meowlnir-policy-server.tls.certResolver={{ matrix_bot_meowlnir_container_labels_policy_server_traefik_tls_certResolver }}
|
||||
{% endif %}
|
||||
|
||||
############################################################
|
||||
# #
|
||||
# /Policy server (/_matrix/policy) #
|
||||
# #
|
||||
############################################################
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ matrix_bot_meowlnir_container_labels_traefik_labels_additional_labels }}
|
||||
@@ -0,0 +1,54 @@
|
||||
#jinja2: lstrip_blocks: True
|
||||
[Unit]
|
||||
Description=Matrix Meowlnir moderation bot
|
||||
{% for service in matrix_bot_meowlnir_systemd_required_services_list %}
|
||||
Requires={{ service }}
|
||||
After={{ service }}
|
||||
{% endfor %}
|
||||
{% for service in matrix_bot_meowlnir_systemd_wanted_services_list %}
|
||||
Wants={{ service }}
|
||||
{% endfor %}
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment="HOME={{ devture_systemd_docker_base_systemd_unit_home_path }}"
|
||||
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-bot-meowlnir 2>/dev/null || true'
|
||||
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-bot-meowlnir 2>/dev/null || true'
|
||||
|
||||
ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} create \
|
||||
--rm \
|
||||
--name=matrix-bot-meowlnir \
|
||||
--log-driver=none \
|
||||
--user={{ matrix_user_uid }}:{{ matrix_user_gid }} \
|
||||
--cap-drop=ALL \
|
||||
--read-only \
|
||||
--network={{ matrix_bot_meowlnir_container_network }} \
|
||||
{% if matrix_bot_meowlnir_container_http_host_bind_port %}
|
||||
-p {{ matrix_bot_meowlnir_container_http_host_bind_port }}:{{ matrix_bot_meowlnir_config_meowlnir_port }} \
|
||||
{% endif %}
|
||||
--label-file={{ matrix_bot_meowlnir_base_path }}/labels \
|
||||
--mount type=bind,src={{ matrix_bot_meowlnir_data_path }},dst=/data \
|
||||
--mount type=bind,src={{ matrix_bot_meowlnir_config_path }},dst=/data/config,ro \
|
||||
{% for arg in matrix_bot_meowlnir_container_extra_arguments %}
|
||||
{{ arg }} \
|
||||
{% endfor %}
|
||||
{{ matrix_bot_meowlnir_container_image }} \
|
||||
/usr/bin/meowlnir --config=/data/config/config.yaml --no-update
|
||||
|
||||
{% if matrix_bot_meowlnir_container_network != 'host' %}
|
||||
{% for network in matrix_bot_meowlnir_container_additional_networks %}
|
||||
ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} network connect {{ network }} matrix-bot-meowlnir
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
ExecStart={{ devture_systemd_docker_base_host_command_docker }} start --attach matrix-bot-meowlnir
|
||||
|
||||
ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-bot-meowlnir 2>/dev/null || true'
|
||||
ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-bot-meowlnir 2>/dev/null || true'
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
SyslogIdentifier=matrix-bot-meowlnir
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2026 Slavi Pantaleev
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
Reference in New Issue
Block a user