diff --git a/plugins/synapse/mas_ b/plugins/synapse/mas_ new file mode 100644 index 00000000..194b7eba --- /dev/null +++ b/plugins/synapse/mas_ @@ -0,0 +1,169 @@ +#!/usr/bin/env sh +# shellcheck shell=dash + +set -e + +: << =cut + +=head1 NAME + +mas_ - Monitor users on a matrix authentication service over admin API + +=head1 APPLICABLE SYSTEMS + +Matrix authentication service (currently only with matrix-synapse systems) + +=head1 CONFIGURATION + +Requires installed curl and jq, a command-line json processor. + +This is a wildcard plugin. It monitors some simple values over the admin API +of matrix authentication service. Link mas_ to this file. The admin +endpoint has to be reachable from the plugin. The adminapi endpoint on the +matrix authentication service has to be enabled and a client (with client_id +and client_id) has to be configured accordingly. + + ln -s /usr/share/munin/plugins/mas_ \ + /etc/munin/plugins/mas_domain.tld + +Set parameters in your munin-node configuration + + [mas_masurl] + env.client_id + env.client_secret + env.bot_names + env.port + env.scheme + env.timeout + +To monitor a matrix authentication service instance on localhost you need +following: + + ln -s /usr/share/munin/plugins/mas_ \ + /etc/munin/plugins/mas_localhost + + [mas_localhost] + env.client_id + env.client_secret + env.port 8081 + env.scheme http + +=head1 AUTHOR + +Copyright (C) 2025 Sebastian L. (https://momou.ch), + +=head1 LICENSE + +GPLv2 + +=head1 MAGIC MARKERS + + #%# family=manual + #%# capabilities=autoconf + +=cut + +# shellcheck disable=SC1090 +. "$MUNIN_LIBDIR/plugins/plugin.sh" + +if [ "${MUNIN_DEBUG:-0}" = 1 ]; then + set -x +fi + +CLIENT_ID="${client_id:-}" +CLIENT_SECRET="${client_secret:-}" +PORT="${port:-443}" +QUERY_LIMIT="${query_limit:-10000}" +MASSERVER="${0##*mas_}" +SCHEME="${scheme:-https}://" +TIMEOUT="${timeout:-2}" +CLEANMASSERVER="$(clean_fieldname "${MASSERVER}")" + +get_access_token () { + ACCESS_TOKEN=$(curl -s -f -m "${TIMEOUT}" -u "$CLIENT_ID:$CLIENT_SECRET" -d "grant_type=client_credentials&scope=urn:mas:admin" "${SCHEME}${MASSERVER}/oauth2/token" | jq -r '.access_token') +} + +fetch_url () { + get_access_token + curl -s -f -m "${TIMEOUT}" "$@" +} + +case $1 in + + autoconf) + if [ ! -x "$(command -v curl)" ]; then + echo "no (curl not found)" + elif [ ! -x "$(command -v jq)" ]; then + echo "no (jq not found)" + else + get_access_token + fetch_url -I -H "Authorization: Bearer ${ACCESS_TOKEN}" -I "${SCHEME}${MASSERVER}:${PORT}/api/admin/v1/users" \ + | grep -iq "Content-Type: application/json" \ + && echo "yes" \ + || echo "no (invalid or empty response from matrix authentication service admin api)" + fi + exit 0 + ;; + config) + +cat << EOM +multigraph mas_users_${CLEANMASSERVER} +graph_title Users on ${MASSERVER} (matrix authentication service) +graph_args --base 1000 -l 0 +graph_printf %.0lf +graph_vlabel users +graph_info users +graph_category chat +registered.label registered users +registered.info registered users +registered.min 0 +active.label active users +active.info active users +active.min 0 +bots.label active bots +bots.info active bots +bots.min 0 +deactivated.label deactivated users +deactivated.info deactivated users +deactivated.min 0 +sessions.label user sessions +sessions.info user sessions +sessions.min 0 +sessions.draw LINE2 +EOM + exit 0 + ;; + +esac + +mktemp_command="${mktemp_command:-/usr/bin/mktemp}" +USERS_FILE=$($mktemp_command) || exit 73 +trap 'rm -f "$USERS_FILE"' EXIT + +get_access_token +fetch_url -g -H "Authorization: Bearer ${ACCESS_TOKEN}" "${SCHEME}${MASSERVER}:${PORT}/api/admin/v1/users?page[first]=${QUERY_LIMIT}" > "$USERS_FILE" +sessions=$(fetch_url -g -H "Authorization: Bearer ${ACCESS_TOKEN}" "${SCHEME}${MASSERVER}:${PORT}/api/admin/v1/user-sessions?filter[status]=active&page[first]=${QUERY_LIMIT}" | jq .meta[]) + +echo "multigraph mas_users_${CLEANMASSERVER}" +if total="$(jq -r .meta[] "$USERS_FILE" | grep -E "^[0-9]+$")"; then + if [ -n "$bot_names" ]; then + bot_names_grep=$(echo "$bot_names" | tr ' ' '|') + bot_names_grep="$bot_names_grep|" + fi + active_bots=$(jq '.data[] | select(.attributes.locked_at == null)' "$USERS_FILE" | grep -cE "\"username\": \"${bot_names_grep}.*bot\"") + deactivated_users=$(jq '.data[] | select(.attributes.locked_at != null)' "$USERS_FILE" | grep -E "\"username\": \"" | { grep -vcE "\"${bot_names_grep}.*bot\"" || true; }) + registered_users=$(( total - active_bots )) + active_users=$(( registered_users - deactivated_users )) + + echo registered.value "$registered_users" + echo active.value "$active_users" + echo bots.value "$active_bots" + echo deactivated.value "$deactivated_users" + echo sessions.value "$sessions" +else + echo "registered.value U" + echo "active.value U" + echo "bots.value U" + echo "deactivated.value U" + echo "sessions.value U" +fi diff --git a/plugins/synapse/synapse_ b/plugins/synapse/synapse_ index 768771da..8e241728 100644 --- a/plugins/synapse/synapse_ +++ b/plugins/synapse/synapse_ @@ -21,6 +21,10 @@ This is a wildcard plugin. It monitors some simple values over the admin API of synapse matrix homeserver. Link synapse_ to this file. The admin endpoint has to be reachable from the plugin. +If matrix authentication service is used, env.disable_users has to be set to +false and the env.auth_token has to be a compatibility token from the matrix +authentication service. + ln -s /usr/share/munin/plugins/synapse_ \ /etc/munin/plugins/synapse_domain.tld @@ -28,6 +32,7 @@ Set parameters in your munin-node configuration [synapse_homeserverurl] env.auth_token + env.disable_users env.interval env.port env.admin_api_path @@ -48,6 +53,9 @@ To monitor a synapse instance on localhost you need following: It's advised to use a dedicated munin bot account (user_type bot) with admin rights on your matrix synapse server for this plugin. +If matrix authentication service is enabled on the matrix synapse server, +disable the users check and use a compatibility token. + =head1 AUTHOR Copyright (C) 2024 Sebastian L. (https://momou.ch), @@ -71,6 +79,7 @@ if [ "${MUNIN_DEBUG:-0}" = 1 ]; then fi AUTH_TOKEN="${auth_token:-}" +DISABLE_USERS="${disable_users:-false}" INTERVAL="${interval:-300}" PORT="${port:-443}" ADMIN_API_PATH="${admin_api_path:-/_synapse/admin}" @@ -101,8 +110,9 @@ case $1 in exit 0 ;; config) - -cat << EOM + USERS_CONFIG="" + if [ "$DISABLE_USERS" != "true" ]; then + USERS_CONFIG=" multigraph synapse_users_${CLEANHOMESERVER} graph_title Synapse users on ${HOMESERVER} graph_args --base 1000 -l 0 @@ -128,7 +138,11 @@ deactivated_users.info deactivated users deactivated_users.min 0 erased_users.label erased users erased_users.info erased users -erased_users.min 0 +erased_users.min 0" + fi + +cat << EOM +$USERS_CONFIG multigraph synapse_rooms_${CLEANHOMESERVER} graph_title Synapse spaces and rooms on ${HOMESERVER} graph_args --base 1000 -l 0 @@ -168,45 +182,48 @@ EOM esac -mktemp_command="${mktemp_command:-/usr/bin/mktemp}" -USERS_FILE=$($mktemp_command) || exit 73 -trap 'rm -f "$USERS_FILE"' EXIT - -fetch_url -H "Authorization: Bearer ${AUTH_TOKEN}" "${SCHEME}${HOMESERVER}:${PORT}${ADMIN_API_PATH}/v2/users?deactivated=true&limit=${QUERY_LIMIT}" > "$USERS_FILE" +if [ "$DISABLE_USERS" != "true" ]; then + mktemp_command="${mktemp_command:-/usr/bin/mktemp}" + USERS_FILE=$($mktemp_command) || exit 73 + trap 'rm -f "$USERS_FILE"' EXIT + fetch_url -H "Authorization: Bearer ${AUTH_TOKEN}" "${SCHEME}${HOMESERVER}:${PORT}${ADMIN_API_PATH}/v2/users?deactivated=true&limit=${QUERY_LIMIT}" > "$USERS_FILE" +fi ROOMS=$(fetch_url -H "Authorization: Bearer ${AUTH_TOKEN}" "${SCHEME}${HOMESERVER}:${PORT}${ADMIN_API_PATH}/v1/rooms?limit=${QUERY_LIMIT}") REPORTS=$(fetch_url -H "Authorization: Bearer ${AUTH_TOKEN}" "${SCHEME}${HOMESERVER}:${PORT}${ADMIN_API_PATH}/v1/event_reports" | jq .total) -echo "multigraph synapse_users_${CLEANHOMESERVER}" -if total_number="$(jq -r .total "$USERS_FILE" | grep -E "^[0-9]+$")"; then - puppets="$(jq -r '.users[] | select(.user_type!="bot") | select(.user_type!="support") | select(.last_seen_ts==null)' "$USERS_FILE")" - bots="$(jq -r '.users[] | select(.user_type=="bot")' "$USERS_FILE")" - users="$(jq -r '.users[] | select(.user_type!="support") | select(.user_type!="bot") | select(.last_seen_ts!=null)' "$USERS_FILE")" +if [ "$DISABLE_USERS" != "true" ]; then + echo "multigraph synapse_users_${CLEANHOMESERVER}" + if total_number="$(jq -r .total "$USERS_FILE" | grep -E "^[0-9]+$")"; then + puppets="$(jq -r '.users[] | select(.user_type!="bot") | select(.user_type!="support") | select(.last_seen_ts==null)' "$USERS_FILE")" + bots="$(jq -r '.users[] | select(.user_type=="bot")' "$USERS_FILE")" + users="$(jq -r '.users[] | select(.user_type!="support") | select(.user_type!="bot") | select(.last_seen_ts!=null)' "$USERS_FILE")" - puppets_number="$(echo "$puppets" | grep -c '"last_seen_ts": null')" - bots_number="$(echo "$bots" | grep -c '"user_type": "bot"')" - virtual_users_number=$(( puppets_number + bots_number )) - total_registered_number=$(( total_number - virtual_users_number )) + puppets_number="$(echo "$puppets" | grep -c '"last_seen_ts": null')" + bots_number="$(echo "$bots" | grep -c '"user_type": "bot"')" + virtual_users_number=$(( puppets_number + bots_number )) + total_registered_number=$(( total_number - virtual_users_number )) - # Convert to miliseconds - time_ms=$(($(date +%s) * 1000)) - interval_ms=$((INTERVAL * 1000)) - time_interval_ago=$(( time_ms - interval_ms )) - last_seen_times_ms=$(echo "$users" | jq -r 'select(.deactivated==false)' | grep -E "\"last_seen_ts\": [0-9]+") - online_users_number="$(echo "$last_seen_times_ms" | awk -v "count=0" -F": " '$2 > "'$time_interval_ago'" {count++} END {print count}')" - - echo total_registered.value "$total_registered_number" - echo active_users.value "$(echo "$users" | grep -c '"deactivated": false')" - echo bots.value "$(echo "$bots" | grep -c '"deactivated": false')" - echo online_users.value "$online_users_number" - echo deactivated_users.value "$(echo "$users" | grep -c '"deactivated": true')" - echo erased_users.value "$(echo "$users" | grep -c '"erased": true')" -else - echo "total_registered.value U" - echo "active_users.value U" - echo "bots.value U" - echo "online_users.value U" - echo "deactivated_users.value U" - echo "erased_users.value U" + # Convert to miliseconds + time_ms=$(($(date +%s) * 1000)) + interval_ms=$((INTERVAL * 1000)) + time_interval_ago=$(( time_ms - interval_ms )) + last_seen_times_ms=$(echo "$users" | jq -r 'select(.deactivated==false)' | grep -E "\"last_seen_ts\": [0-9]+") + online_users_number="$(echo "$last_seen_times_ms" | awk -v "count=0" -F": " '$2 > "'$time_interval_ago'" {count++} END {print count}')" + + echo total_registered.value "$total_registered_number" + echo active_users.value "$(echo "$users" | grep -c '"deactivated": false')" + echo bots.value "$(echo "$bots" | grep -c '"deactivated": false')" + echo online_users.value "$online_users_number" + echo deactivated_users.value "$(echo "$users" | grep -c '"deactivated": true')" + echo erased_users.value "$(echo "$users" | grep -c '"erased": true')" + else + echo "total_registered.value U" + echo "active_users.value U" + echo "bots.value U" + echo "online_users.value U" + echo "deactivated_users.value U" + echo "erased_users.value U" + fi fi echo multigraph synapse_rooms_"${CLEANHOMESERVER}"