jilo/jilo-cli

746 lines
24 KiB
Bash

#!/usr/bin/env bash
###
# JItsi Log Observer - command line interface
#
# Bash script for Jitsi Meet components (Videobridge, Jicofo, etc.) logs parsing
# Command line interface (jilo-cli)
###
VERSION="0.1"
RELEASE_DATE="2024-06-12"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" #"
### Configuration file (overrides default configs)
CONFIG_FILE="$SCRIPT_DIR/jilo.conf"
### Default configuration
# Default database type (sqlite, mysql|mariadb)
DEFAULT_DB_TYPE="sqlite"
# Default SQLite database file
DEFAULT_DB="$SCRIPT_DIR/jilo.db"
# Default MySQL/MariaDB configuration
DEFAULT_MYSQL_HOST="localhost"
DEFAULT_MYSQL_USER="jilo"
DEFAULT_MYSQL_PASS="jilopass"
DEFAULT_MYSQL_DB="jilo_db"
DEFAULT_NORMAL_TEXT="\033[0m"
DEFAULT_IMPORTANT_TEXT="\033[1m"
# Load configurations from the config file if it exists
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
fi
# use default values if not overriden by config file
DB=${DB:-$DEFAULT_DB}
DB_TYPE=${DB_TYPE:-$DEFAULT_DB_TYPE}
MYSQL_HOST=${MYSQL_HOST:-$DEFAULT_MYSQL_HOST}
MYSQL_USER=${MYSQL_USER:-$DEFAULT_MYSQL_USER}
MYSQL_PASS=${MYSQL_PASS:-$DEFAULT_MYSQL_PASS}
MYSQL_DB=${MYSQL_DB:-$DEFAULT_MYSQL_DB}
NORMAL_TEXT=${NORMAL_TEXT:-$DEFAULT_NORMAL_TEXT}
IMPORTANT_TEXT=${IMPORTANT_TEXT:-$DEFAULT_IMPORTANT_TEXT}
### DB queries
## conference related
db_conferences_all_formatted_template="
SELECT
c.jitsi_component,
c.conference_id,
c.conference_name,
name_counts.name_count,
c.conference_host
FROM
conferences c
JOIN (
SELECT
conference_name,
COUNT(*) AS name_count
FROM
conferences
GROUP BY
conference_name
) AS name_counts ON c.conference_name = name_counts.conference_name
ORDER BY
c.id;"
db_conferences_id_template="SELECT * FROM conferences WHERE conference_id='%s';"
db_conferences_name_template="SELECT * FROM conferences WHERE conference_name='%s';"
db_conference_events_template="SELECT * FROM conference_events WHERE conference_id='%s';"
db_conference_by_id_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_id = '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_id = '%s'
ORDER BY
pe.time;"
db_conference_by_name_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
c.conference_name = '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
c.conference_name = '%s'
ORDER BY
pe.time;"
## participant related
db_participants_all_template="SELECT jitsi_component, endpoint_id, conference_id FROM participants ORDER BY id;"
db_participants_id_template="SELECT * FROM participants WHERE endpoint_id='%s';"
db_participant_events_template="SELECT * FROM participant_events WHERE participant_id='%s';"
db_participants_endpoint_template="SELECT * FROM participants WHERE endpoint_id='%s';"
db_participants_conference_template="SELECT * FROM participants WHERE conference_id='%s';"
db_participants_statsid_template="SELECT * FROM participant_events WHERE event_type = 'stats_id' AND event_param LIKE '%%%s%%';"
db_participants_ip_template="SELECT * FROM participant_events WHERE event_type = 'pair selected' AND event_param='%s';"
db_conference_by_participant_id_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
p.endpoint_id = '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
participant_id = '%s'
ORDER BY
pe.time;"
db_participant_by_conference_id_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_param = '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_param = '%s'
ORDER BY
pe.time;"
db_participant_by_stats_id_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'stats_id' AND pe.event_param LIKE '%%%s%%'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'stats_id' AND event_param LIKE '%%%s%%'
ORDER BY
pe.time;"
db_participant_by_ip_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.event_type = 'pair selected' AND pe.event_param = '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_type = 'pair selected' AND event_param = '%s'
ORDER BY
pe.time;"
## time period related
db_events_by_period_template="
SELECT
pe.time,
c.conference_id,
c.conference_name,
c.conference_host,
pe.loglevel,
pe.event_type,
p.endpoint_id AS participant_id,
pe.event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
LEFT JOIN
participants p ON c.conference_id = p.conference_id
LEFT JOIN
participant_events pe ON p.endpoint_id = pe.participant_id
WHERE
pe.time >= '%s' AND pe.time <= '%s'
UNION
SELECT
ce.time AS event_time,
c.conference_id,
c.conference_name,
c.conference_host,
ce.loglevel,
ce.conference_event AS event_type,
NULL AS participant_id,
ce.conference_param AS event_param
FROM
conferences c
LEFT JOIN
conference_events ce ON c.conference_id = ce.conference_id
WHERE
event_time >= '%s' AND event_time <= '%s'
ORDER BY
pe.time;"
help="Usage:
$0 [OPTION]
Options:
--conference|-c [conference ID or name] - show specific conference(s), all of empty
--participant|-p [participant endpoint ID, conference ID, participant IP, or participant stats ID] - show specific participant(s), all if empty
--time|-t - show stats for a time interval
--help|-h - show this help message
--version|-V - show version"
version="JILO Jitsi Logs Observer command line client
jilo-cli_${VERSION}_${RELEASE_DATE}
version $VERSION
released on $RELEASE_DATE"
###
# First we check for requirements
check_requirements() {
# required programs, anything non-bash - edit as needed
# deb packages - sqlite3, util-linux (column)
local required_programs=("sqlite3" "column")
local requirements_missing=''
for program in "${required_programs[@]}"; do
if ! command -v "$program" &> /dev/null; then
requirements_missing+="$program, "
fi
done
if [[ "$requirements_missing" != '' ]]; then
requirements_missing=${requirements_missing::-2}
echo "Error: $requirements_missing - not found. Please install to proceed."
exit 1
fi
}
check_requirements
###
# DB functions for Sqlite3 and for MySQL/MariaDB
# execute a query and return the result
db_query() {
local query=$1
if [[ "$DB_TYPE" == "sqlite" ]]; then
sqlite3 "$DB" "$query"
elif [[ "$DB_TYPE" == "mysql" || "$DB_TYPE" == "mariadb" ]]; then
mysql -h "$MYSQL_HOST" -u "$MYSQL_USER" -p "$MYSQL_PASS" -D "$MYSQL_DB" -se "$query"
else
echo "Error: unknown database type $DB_TYPE."
exit 1
fi
}
### input parameters parsing
# time parameters
parse_time_range() {
local time_range="$1"
## exact times
# exact date given (YYYY-MM-DD)
if [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
from_time="${BASH_REMATCH[0]}"
until_time="${BASH_REMATCH[0]}"
# exact month given (YYYY-MM)
elif [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2})$ ]]; then
from_time="${BASH_REMATCH[0]}-01"
until_time="${BASH_REMATCH[0]}-31"
# exact year given (YYYY)
elif [[ "$time_range" =~ ^([0-9]{4})$ ]]; then
from_time="${BASH_REMATCH[0]}-01-01"
until_time="${BASH_REMATCH[0]}-12-31"
## exact periods
# from date to date
elif [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2}):([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
from_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}"
until_time="${BASH_REMATCH[4]}-${BASH_REMATCH[5]}-${BASH_REMATCH[6]}"
# from month to month
elif [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2}):([0-9]{4})-([0-9]{2})$ ]]; then
from_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-01"
until_time="${BASH_REMATCH[3]}-${BASH_REMATCH[4]}-31"
# from year to year
elif [[ "$time_range" =~ ^([0-9]{4}):([0-9]{4})$ ]]; then
from_time="${BASH_REMATCH[1]}-01-01"
until_time="${BASH_REMATCH[2]}-12-31"
## only end time given
# from begining until date (:YYYY-MM-DD)
elif [[ "$time_range" =~ ^:([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
from_time="0000-01-01"
until_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}"
# from begining until month (:YYYY-MM)
elif [[ "$time_range" =~ ^:([0-9]{4})-([0-9]{2})$ ]]; then
from_time="0000-01-01"
until_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-31"
# from begining until year (:YYYY)
elif [[ "$time_range" =~ ^:([0-9]{4})$ ]]; then
from_time="0000-01-01"
until_time="${BASH_REMATCH[0]}-12-31"
## only start time given
# from date until end (YYYY-MM-DD:)
elif [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2}):$ ]]; then
from_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}"
until_time="9999-12-31"
# from month until end (YYYY-MM:)
elif [[ "$time_range" =~ ^([0-9]{4})-([0-9]{2}):$ ]]; then
from_time="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-01"
until_time="9999-12-31"
# from year until end (YYYY:)
elif [[ "$time_range" =~ ^([0-9]{4}):$ ]]; then
from_time="${BASH_REMATCH[1]}-01-01"
until_time="9999-12-31"
else
echo -e "Invalid time range. Expected formats:
- exact times
YYYY-MM-DD, YYYY-MM, YYYY
- exact periods
YYYY-MM-DD:YYYY-MM-DD, YYYY-MM:YYYY-MM, YYYY:YYYY
- from begining to given time
:YYYY-MM-DD, :YYYY-MM, :YYYY
- from given time until end
YYYY-MM-DD:, YYYY-MM:, YYYY:" >&2
exit 1
fi
time_range_specified=true
}
### commandline options
cmd=""
conference_arg=""
participant_arg=""
from_time=""
until_time=""
time_range_specified=false
while [[ $# -gt 0 ]]; do
case "$1" in
-c | --conference )
cmd="--conference"
if [[ -n "$2" && "$2" != -* ]]; then
conference_arg="$2"
shift 2
else
shift
fi
;;
-p | --participant)
cmd="--participant"
if [[ -n "$2" && "$2" != -* ]]; then
participant_arg="$2"
shift 2
else
shift
fi
;;
-t | --time)
cmd="--time"
if [[ -n "$2" && "$2" != -* ]]; then
parse_time_range "$2"
shift 2
else
echo "Option -t needs time range argument in format 'from-time - until-time', YYYY-MM-DD - YYYY-MM-DD" >&2
exit 1
fi
;;
-h | --help)
echo -e "$help"
exit 0
;;
-V | --version)
echo -e "$version"
exit 0
;;
*)
echo "Invalid option: $1" >&2
echo -e "$help"
exit 1
;;
esac
done
case "$cmd" in
--conference)
# the argument to "--conference" can be either ID or name
if [[ -n "$conference_arg" ]]; then
db_conferences_id=$(printf "$db_conferences_id_template" "$conference_arg")
conferences_id=$(db_query "$db_conferences_id")
db_conferences_name=$(printf "$db_conferences_name_template" "$conference_arg")
conferences_name=$(db_query "$db_conferences_name")
# we check if the argument to "--conference" is a conference ID
# conference ID is unique, so we show that conference
if [[ -n "$conferences_id" ]]; then
db_conference=$(printf "$db_conference_by_id_template" "$conference_arg" "$conference_arg")
mapfile -t conference_array < <(db_query "$db_conference")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$IMPORTANT_TEXT$conference_id$NORMAL_TEXT\t$conference_name\t$conference_host\t$loglevel\t$participant_id\t$event_type\t$event_param\n"
done
# output
echo -e "$output" | column -t -s $'\t'
# then we check if the argument to "--conference" is a conference name
# if so, we show all matching conferences (conference names are not unique)
elif [[ -n "$conferences_name" ]]; then
db_conference=$(printf "$db_conference_by_name_template" "$conference_arg" "$conference_arg")
mapfile -t conference_array < <(db_query "$db_conference")
# prepare the header
output="time\tconf ID\tconf name\tconf host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$conference_id\t$IMPORTANT_TEXT$conference_name$NORMAL_TEXT\t$conference_host\t$loglevel\t$participant_id\t$event_type\t$event_param\n"
done
# output
echo -e "$output" | column -t -s $'\t'
# nothing found for neither conference ID or name
else
echo "No match found for \"$conference_arg\""
fi
exit 0
else
# if no argument is given, we show all the conferences
mapfile -t conference_array < <(db_query "$db_conferences_all_formatted_template")
# prepare the header
output="component\tconference ID\tconference name\tname count\tconference host\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r jitsi_component conference_id conference_name name_count conference_host <<< "$row"
output+="$jitsi_component\t$conference_id\t$conference_name\t$name_count\t$conference_host\n"
done
# output
echo -e "$output" | column -t -s $'\t'
exit 0
fi
;;
--participant)
# the argument to "--participant" can be endpointID, conferenceID, startsID, IP address
# we check for all ot them and show the matches
if [[ -n "$participant_arg" ]]; then
db_participants_endpoint=$(printf "$db_participants_endpoint_template" "$participant_arg")
participants_endpoint=$(db_query "$db_participants_endpoint")
db_participants_conference=$(printf "$db_participants_conference_template" "$participant_arg")
participants_conference=$(db_query "$db_participants_conference")
db_participants_statsid=$(printf "$db_participants_statsid_template" "$participant_arg")
participants_statsid=$(db_query "$db_participants_statsid")
db_participants_ip=$(printf "$db_participants_ip_template" "$participant_arg")
participants_ip=$(db_query "$db_participants_ip")
if [[ -n $participants_endpoint ]]; then
db_participant=$(printf "$db_conference_by_participant_id_template" "$participant_arg" "$participant_arg")
mapfile -t conference_array < <(db_query "$db_participant")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$conference_id\t$conference_name\t$conference_host\t$loglevel\t$IMPORTANT_TEXT$participant_id$NORMAL_TEXT\t$event_type\t$event_param\n"
done
# output
echo -e "$output" | column -t -s $'\t'
fi
if [[ -n $participants_conference ]]; then
db_participant=$(printf "$db_participant_by_conference_id_template" "$participant_arg" "$participant_arg")
mapfile -t conference_array < <(db_query "$db_participant")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$IMPORTANT_TEXT$conference_id$NORMAL_TEXT\t$conference_name\t$conference_host\t$loglevel\t$participant_id\t$event_type\t$event_param\n"
done
# output
echo -e "$output" | column -t -s $'\t'
fi
if [[ -n $participants_statsid ]]; then
db_participant=$(printf "$db_participant_by_stats_id_template" "$participant_arg" "$participant_arg")
mapfile -t conference_array < <(db_query "$db_participant")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$conference_id\t$conference_name\t$conference_host\t$loglevel\t$participant_id\t$event_type\t$IMPORTANT_TEXT$event_param$NORMAL_TEXT\n"
done
# output
echo -e "$output" | column -t -s $'\t'
fi
if [[ -n $participants_ip ]]; then
db_participant=$(printf "$db_participant_by_ip_template" "$participant_arg" "$participant_arg")
mapfile -t conference_array < <(db_query "$db_participant")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${conference_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$conference_id\t$conference_name\t$conference_host\t$loglevel\t$participant_id\t$event_type\t$IMPORTANT_TEXT$event_param$NORMAL_TEXT\n"
done
# output
echo -e "$output" | column -t -s $'\t'
fi
exit 0
else
db_participants_all=$(printf "$db_participants_all_template" )
mapfile -t participant_array < <(db_query "$db_participants_all")
# prepare the header
output="component\tparticipant ID\tconference ID\n"
# prepare the formatted rows
for row in "${participant_array[@]}"; do
IFS='|' read -r jitsi_component endpoint_id conference_id <<< "$row"
output+="$jitsi_component\t$endpoint_id\t$conference_id\n"
done
# output
echo -e "$output" | column -t -s $'\t'
exit 0
fi
;;
--time)
if [[ "$time_range_specified" == true ]]; then
echo "Events in the period $from_time - $until_time"
db_events_time=$(printf "$db_events_by_period_template" "$from_time" "$until_time" "$from_time" "$until_time")
mapfile -t events_array < <(db_query "$db_events_time")
# prepare the header
output="time\tconference ID\tconference name\tconference host\tloglevel\tparticipant ID\tevent\tparameter\n"
# prepare the formatted rows
for row in "${events_array[@]}"; do
IFS='|' read -r time conference_id conference_name conference_host loglevel event_type participant_id event_param <<< "$row"
output+="$time\t$conference_id\t$conference_name\t$conference_host\t$loglevel\t$event_type\t$participant_id\t$event_param\n"
done
# output
echo -e "$output" | column -t -s $'\t'
exit 0
fi
;;
*)
echo -e "$help"
exit 1
;;
esac