.claude/settings.local.json 🔗
@@ -0,0 +1,8 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(shellcheck:*)"
+ ],
+ "deny": []
+ }
+}
Amolith created
.claude/settings.local.json | 8 ++
README.md | 79 +++++++++++++++++++++
mumblingherald | 146 +++++++++++++++++++++++++++++++++++++++
3 files changed, 233 insertions(+)
@@ -0,0 +1,8 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(shellcheck:*)"
+ ],
+ "deny": []
+ }
+}
@@ -0,0 +1,79 @@
+<!--
+SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+
+SPDX-License-Identifier: Unlicense
+-->
+
+# The Mumbling Herald
+
+A grumpy old herald who watches your Mumble server and announces comings and goings via XMPP.
+
+## Full setup
+
+- Create a herald user with minimal permissions
+ ```bash
+ sudo useradd -r -s /bin/false -d /var/lib/mumblingherald -m mumblingherald
+ sudo usermod -a -G mumble-server mumblingherald # if mumble-server group exists for log access
+ ```
+- Copy script to user's directory, make it executable, and set ownership
+ ```bash
+ sudo cp mumblingherald /var/lib/mumblingherald
+ sudo chown mumblingherald:mumblingherald /var/lib/mumblingherald/mumblingherald
+ sudo chmod 700 /var/lib/mumblingherald/mumblingherald
+ ```
+- Edit the script to configure details
+ - `MUMBLE_LOG_FILE`: path to your Mumble server log
+ - `XMPP_RECIPIENTS`: recipient JID for notifications
+ - `MONITORED_CHANNELS`: display name of channels to watch
+- Install go-sendxmpp
+ - Download the latest binary from the [go-sendxmpp releases page](https://salsa.debian.org/mdosch/go-sendxmpp/-/releases)
+ - Extract and copy to system location
+ ```bash
+ sudo cp go-sendxmpp /usr/local/bin/
+ sudo chmod 755 /usr/local/bin/go-sendxmpp
+ ```
+- Create config directory
+ ```bash
+ sudo -u mumblingherald mkdir -p /var/lib/mumblingherald/.config/go-sendxmpp
+ ```
+- Create XMPP config in `/var/lib/mumblingherald/.config/go-sendxmpp/config` (edit values!)
+ ```bash
+ username: herald@your-xmpp-server.com
+ password: your-password
+ ```
+ - Correct permissions
+ ```bash
+ chmod 600 /var/lib/mumblingherald/.config/go-sendxmpp/config
+ ```
+- Create systemd service in `/etc/systemd/system/mumblingherald.service`
+ ```ini
+ [Unit]
+ Description=The Mumbling Herald - Mumble event notifier over XMPP
+ After=network.target
+ [Service]
+ Type=simple
+ User=mumblingherald
+ Group=mumble-server
+ WorkingDirectory=/var/lib/mumblingherald
+ Environment=HOME=/var/lib/mumblingherald
+ ExecStart=/var/lib/mumblingherald/mumblingherald
+ Restart=always
+ RestartSec=5
+ [Install]
+ WantedBy=multi-user.target
+ ```
+- Start the herald
+ ```bash
+ sudo systemctl daemon-reload
+ sudo systemctl enable --now mumblingherald
+ ```
+- Check status
+ ```bash
+ sudo systemctl status mumblingherald
+ ```
+
+## Logs
+
+```bash
+sudo journalctl -u mumblingherald -f
+```
@@ -0,0 +1,146 @@
+#!/bin/bash
+
+# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+#
+# SPDX-License-Identifier: Unlicense
+
+# TheMumblingHerald
+# Monitors Mumble server logs and sends XMPP notifications when users join specified channels
+
+# Configuration
+MUMBLE_LOG_FILE="/var/log/mumble-server/mumble-server.log"
+XMPP_CONFIG_FILE="$HOME/.config/go-sendxmpp/config"
+XMPP_RECIPIENTS="groupchat@conference.example.com"
+
+# List of channels to monitor (channel names)
+# Add or remove channels as needed
+MONITORED_CHANNELS=(
+ "Room 4"
+ "Room 5"
+)
+
+# Function to check if a channel should be monitored
+is_monitored_channel() {
+ local channel="$1"
+ for monitored in "${MONITORED_CHANNELS[@]}"; do
+ if [[ "$channel" == "$monitored" ]]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+# Arrays of varied messages for the mumbling herald
+# shellcheck disable=SC2034
+JOIN_MESSAGES=(
+ "➡️ {USERNAME} stumbles into {CHANNEL}"
+ "➡️ {USERNAME} wanders into {CHANNEL}"
+ "➡️ *clears throat* {USERNAME} appears in {CHANNEL}"
+ "➡️ {USERNAME} shuffles into {CHANNEL}"
+ "➡️ Behold! {USERNAME} manifests in {CHANNEL}"
+ "➡️ {USERNAME} finds their way to {CHANNEL}"
+ "➡️ Lo! {USERNAME} enters {CHANNEL}"
+ "➡️ {USERNAME} materializes in {CHANNEL}"
+)
+
+# shellcheck disable=SC2034
+LEAVE_MESSAGES=(
+ "⬅️ Oh, what? *ahem* {USERNAME} fled... somewhere *waves hand*"
+ "⬅️ {USERNAME} vanishes into the void..."
+ "⬅️ {USERNAME} disappeared! Just like that..."
+ "⬅️ *waves dismissively* Off goes {USERNAME}..."
+ "⬅️ Farewell to thee, {USERNAME}..."
+ "⬅️ {USERNAME} has made their exit..."
+ "⬅️ {USERNAME} has fled our realm..."
+ "⬅️ *sighs* And thus {USERNAME} departs..."
+)
+
+# Function to get a random message from an array
+get_random_message() {
+ local -n arr=$1
+ echo "${arr[$RANDOM % ${#arr[@]}]}"
+}
+
+# Function to send XMPP notification
+send_notification() {
+ local message="$1"
+ echo "$message" | go-sendxmpp -c -f "$XMPP_CONFIG_FILE" "$XMPP_RECIPIENTS"
+}
+
+# Function to parse log line and extract user move information
+parse_move_event() {
+ local log_line="$1"
+
+ # Extract user and destination channel
+ # Format: <W>2025-07-01 14:07:32.352 1 => <28:Amolith(5)> Moved Amolith:28(5) to Room 5[53:20]
+ if [[ "$log_line" =~ \<W\>[0-9-]+\ [0-9:.]+.*\>\ Moved\ ([^:]+):[0-9]+\([0-9]+\)\ to\ ([^\[]+)\[ ]]; then
+ local username="${BASH_REMATCH[1]}"
+ local channel="${BASH_REMATCH[2]}"
+
+ # Check if this channel should be monitored
+ if is_monitored_channel "$channel"; then
+ local join_msg
+ join_msg=$(get_random_message JOIN_MESSAGES)
+ local message="${join_msg//\{USERNAME\}/$username}"
+ message="${message//\{CHANNEL\}/$channel}"
+ echo "*herald mumbles something about $username*"
+ send_notification "$message"
+ fi
+ fi
+}
+
+# Function to parse log line and extract user disconnect information
+parse_disconnect_event() {
+ local log_line="$1"
+
+ # Extract user from disconnect event
+ # Format: <W>2025-07-01 14:19:46.484 1 => <28:Amolith(5)> Connection closed: The remote host closed the connection [1]
+ if [[ "$log_line" =~ \<W\>[0-9-]+\ [0-9:.]+.*\<[0-9]+:([^\(]+)\([0-9]+\)\>\ Connection\ closed: ]]; then
+ local username="${BASH_REMATCH[1]}"
+
+ local leave_msg
+ leave_msg=$(get_random_message LEAVE_MESSAGES)
+ local message="${leave_msg//\{USERNAME\}/$username}"
+ echo "*herald notices someone's absence and sighs*"
+ send_notification "$message"
+ fi
+}
+
+# Main monitoring loop
+echo "🗣️ Shaking the mumbling herald awake..."
+echo "📜 Herald will watch these sacred halls: ${MONITORED_CHANNELS[*]}"
+echo "👁️ Peering into the mystical log: $MUMBLE_LOG_FILE"
+echo "📨 Herald will announce to: $XMPP_RECIPIENTS"
+echo "🛑 Press Ctrl+C to put the herald back to sleep"
+echo
+
+# Check if log file exists
+if [[ ! -f "$MUMBLE_LOG_FILE" ]]; then
+ echo "💀 *herald panics* The sacred scroll is missing! $MUMBLE_LOG_FILE"
+ echo "🔧 Fix the MUMBLE_LOG_FILE path, good sir!"
+ exit 1
+fi
+
+# Check if go-sendxmpp is available
+if ! command -v go-sendxmpp &>/dev/null; then
+ echo "📯 *herald looks confused* Where's my speaking trumpet? (go-sendxmpp missing)"
+ echo "📦 Install go-sendxmpp so I can herald properly!"
+ exit 1
+fi
+
+# Check if XMPP config file exists
+if [[ ! -f "$XMPP_CONFIG_FILE" ]]; then
+ echo "⚠️ *herald squints* My address book seems to be missing: $XMPP_CONFIG_FILE"
+ echo "📝 Please inscribe your XMPP credentials!"
+fi
+
+# Start tailing the log file and processing events
+echo "👂 *herald puts ear to the wall and begins listening intently*"
+tail -F "$MUMBLE_LOG_FILE" | while read -r line; do
+ # Process lines that contain "Moved" or "Connection closed" events
+ if [[ "$line" == *"Moved"* ]]; then
+ parse_move_event "$line"
+ elif [[ "$line" == *"Connection closed"* ]]; then
+ parse_disconnect_event "$line"
+ fi
+done