A la poursuite du Bash… (Bourne-again shell !)

Objectif : créer une mécanique de suivi pour un serveur Quake3

Un peu de sémantique pour commencer… nc, tee, pid, grep, pgrep, wget, mkfifo, tcpdump, inotifywait, tail, cut, rcon et surtout Quake3 !

Le fichier monitor.sh est à télécharger ici !

PREMIERE ASTUCE DU SYSTEME :
SUIVRE LE FICHIER LOG POUR LES JOUEURS ET FAIRE PARLER LE NABAZTAG

1. Utilisation de inotifywait pour gérer des évènements sur un fichier (le log du jeux)

L’évènement est transmit à un tube nommé ($FIFO) déclaré au préalable qui permet de récupérer l’entrée du fichier…

FIFO="/tmp/inotify1.fifo"
FIFA="/tmp/inotify2.fifo"
if [ ! -e "$FIFO" ] ; then
    mkfifo "$FIFO"
    mkfifo "$FIFA"
fi
inotifywait -m -e modify "$FICHIER" > "$FIFO" &	INOTIFY_PID=$!

2. Une boucle pour parcourir chaque ligne du fichier…

Le tube nommé ($FIFO) est injecté dans une première boucle, celle-ci cherche le mot ‘broadcast’ puis renvoie la ligne dans un second tube nommé ($FIFA), lui même injecté dans une seconde boucle qui permet d’appeler  ‘on_event’ pour le traitement du contenu.

while read ; do
    grep -E "broadcast" "$FICHIER" | uniq | tail -1 > "$FIFA" & INOTIFY_PID=$!
    while read line; do
        on_event "$line"
    done < "$FIFA"
done < "$FIFO"

3. Choix des actions à réaliser en fonction de la ligne récupérée

Les chaines de textes sont ouvertes avec des commandes GREP, CUT pour  rechercher des informations.
Une suite de IF et de CASE permettent de réaliser des actions. (Ici, utiliser l’API du Nabaztag en PHP pour annoncer la présence d’un joueur, la déconnexion…)

local message=$(echo $1 | cut -d'"' -f2)
if echo "$message" | grep -q "\^"; then
    echo "LA CHAINE CONTIENT ^ (message en rapport avec un joueur)"
    action=$(echo "$message" | cut -d'^' -f2 | cut -d' ' -f2  )
    person=$(echo "$message" | cut -d'^' -f1 )
		else
    ...
fi

SECONDE ASTUCE DU SYSTEME :
STOPPER/RELANCER LE SERVEUR EN FONCTIONS DES JOUEURS

1. Mettre le serveur du jeux en pause lorsqu’il n’y à pas de joueurs :

Dès que la fin de la partie est détectée depuis le fichier log du jeu : on interroge le serveur avec le protocole RCON (Remote Connexion) pour obtenir les statistiques et compter les joueurs restant :
La commande ‘echo’ envoie par le biais de nc une chaine UDP qui permet de s’authentifier et lancer une demande de status du serveur.
La chaine est retournée  dans un fichier ($SCORE) dont on compte les lignes. (Impossible de faire ces deux étapes en une seule… 🙁
Si le nb est de 11, c’est qu’il ne reste que des robots dans la map du jeu.
On place le PID du serveur Quake (q3ded) en pause avec la commande kill -STOP

if [ "$action" == "match" ]; then
    echo -e "\xFF\xFF\xFF\xFFrcon "PASS_DU_SERVEUR" status\n" | nc -nu IP_DU_SERVEUR 27960 | tee ${SCORES} >/dev/null 2>&1 &
    nb=$(cat ${SCORES} | wc -l)
    if [ "$nb" == "11" ]; then
        pid=$(pgrep -x q3ded)
        kill -STOP "$pid"
   fi
fi

2. Relancer le serveur lors d’une demande de connexion :

Pour démarrer le serveur en cas d’arrivée d’un joueur, on utilise tcpdump pour surveiller le port de connexion et renvoyer les lignes contenant ‘getchallenge’ vers le fichier log du jeu.
La commande précédente (inotifywait) déclenche la reprise du serveur lors détection la demande de connexion.
On vérifie avant que le serveur (q3ded) ne soit pas déjà lancé.

tcpdump -A -nn -s0 -i any dst port 27960 -l 2> /dev/null | grep --line-buffered 'getchallenge' | tee -a "$FICHIER" >/dev/null 2>&1 &
if [ "$action" == "start" ]; then
    etat=$(ps axc | grep q3ded | awk "{ print \$3 }")
    if [ "$etat" == "T" ]; then
        pid=$(pgrep -x q3ded)
        kill -CONT "$pid"
    fi
fi

Le fichier monitor.sh est à télécharger ici !

LE SCRIPT COMPLET :

#!/bin/sh
#---------------------------------------#
#	E. Grandadam - LCPROD
#
#	lcprod37@gmail.com
#
#   ./monitor.sh > /dev/null &
#---------------------------------------

# CONFIGURATION#---------------------------------------

FIFO="/tmp/inotify1.fifo"
FIFA="/tmp/inotify2.fifo"
FICHIER="/REPERTOIRE_DU_FICHIER_LOG/qconsole.log"
SCORES="/REPERTOIRE_DU_FICHIER_LOG/qscores.log"

# INITIALISATION#---------------------------------------

if [ ! -e "$FIFO" ] ; then   
    mkfifo "$FIFO"
    mkfifo "$FIFA"
fi	

# FONCTIONS#---------------------------------------

on_exit() {
    echo "On Exit"   
    kill $INOTIFY_PID
    killall inotifywait   
    rm $FIFO
    rm $FIFA   
    exit
}

on_event() {
    local message=$(echo $1 | cut -d'"' -f2)
    if [ "$old" != "$message" ] && [ "$message" ] ; then
        echo "message : $message"
        if echo "$message" | grep -q "\^"; then
            echo "LA CHAINE CONTIENT ^ (message en rapport avec un joueur)"
            action=$(echo "$message" | cut -d'^' -f2 | cut -d' ' -f2  )
            person=$(echo "$message" | cut -d'^' -f1 )
        else
            echo "LA CHAINE NE CONTIENT PAS ^ (message système)"
            if echo "$message" | grep -q "getchallenge"; then
                person="Serveur"
                action="start"
            else
                person=$(echo "$message" | cut -d' ' -f1)
                action=$person
            fi
        fi						

        # CAS DU ROBOT...
        if echo "$message" | grep -q "Bot"; then
            person="Bot $action (bot)"
            action=""
        fi						

        #CAS PARTICULIER
        if echo "$message" | grep -q "renamed"; then
            person=""
            action="renamed"
        fi						

        #FIN DE PARTIE
        if echo "$message" | grep -q "match"; then
            person=""
            action="match"
        fi

        echo "person : $person"
        echo "action : $action"	

        case "$person" in
            "UnnamedPlayer") parle="Joueur sans nom" ;;
            *) parle="Joueur inconnu : $person" ;;
        esac

        case "$action" in
            "connectedn") parle+=" est connecté" ;;
            "disconnectedn") parle+=" est déconnecté" ;;
            "entered") parle+=" est dans la place" ;;
            "Dropped") parle+=" est déconnecté car inactif" ;;
            "fragged") parle="" ;;
            "called") parle="" ;;
            "renamed") parle="" ;;
            "timed") parle="" ;;
            "blue") parle="" ;;
            "red") parle="" ;;
            "Vote") parle="" ;;
            *) parle="" ;;
        esac

        old="$message"	

        if [ "$action" == "match" ]; then
            echo -e "\xFF\xFF\xFF\xFFrcon "PASS_DU_SERVEUR" status\n" | nc -nu IP_DU_SERVEUR 27960 | tee ${SCORES} >/dev/null 2>&1 &
            rr=$(cat ${SCORES} | wc -l)
            if [ "$rr" == "11" ]; then
                parle="stoppé !"
                pid=$(pgrep -x q3ded)
                kill -STOP "$pid"
            fi
        fi

        if [ "$action" == "start" ]; then
            etat=$(ps axc | grep q3ded | awk "{ print \$3 }")
            if [ "$etat" == "T" ]; then
                parle="démarré !"
                pid=$(pgrep -x q3ded)
                kill -CONT "$pid"
            fi
        fi
        if [ "$parle" ]; then
            URL='URL_DE_L_API_DU_NABAZTAG_(Openjabnab)'
            URL+=$(php -r  "echo rawurlencode('$parle');")
            wget -q "$URL" -O /dev/null
            echo "parle : $parle"
        else
            echo "parle : pas !"
        fi
        echo "-------------------------------------"
    fi
}

# ECOUTEUR
#---------------------------------------

tcpdump -A -nn -s0 -i any dst port 27960 -l 2> /dev/null | grep --line-buffered 'getchallenge' | tee -a "$FICHIER" >/dev/null 2>&1 &
inotifywait -m -e modify "$FICHIER" > "$FIFO" &	INOTIFY_PID=$!
trap "on_exit" 2 3 15	

# TRAITEMENT#---------------------------------------

while read ; do
    #echo "Change"
    grep -E "broadcast|getchallenge" "$FICHIER" | uniq | tail -1 > "$FIFA" & INOTIFY_PID=$!
    while read line; do
        on_event "$line"
    done < "$FIFA"
done < "$FIFO"

on_exit

Comments are closed.