#!/bin/sh

# Ce script sert à créer ou détruire une instance Tomcat
# en créant un CATALINA_BASE indépendant.

# L'usage habituel se fait sur une Debian 7 "Wheezy" avec
# tomcat, libapache2-mod-jk et apache2 installé.

# Prérequis :
# - Debian Wheezy (Squeeze non testée)
# - tomcat7 (6 non testée)
# - libapache2-mod-jk avec le workers.properties du module

# Trucs à nettoyer (si quelque chose part en vrille, ou simplement
# pour désinstaller ):
# - supprimer tout le répertoire REP_TOMCAT_MULTIINSTANCE
# - supprimer /etc/default/tomcatN_XXX
# - supprimer /etc/init.d/tomcatN_XXX
# - supprimer les utilisateurs tomcatN_YYYY
# - nettoyer les clusters PostgreSQL

# Version de Tomcat : 6 ou 7
# TODO : voir si on peut le passer en paramètre pour gérer
#        Tomcat 6 & 7 sur la même machine
VERSION_TOMCAT="7"
# Quelques constantes internes
MAX_INSTANCES=999
START_RANGE_TOMCATUID=8000
START_RANGE_TOMCAT_PORT_SYS=15000
START_RANGE_TOMCAT_PORT_HTTP=18000
START_RANGE_TOMCAT_PORT_AJP=19000

# Initialisation des variables globales
MODE_CREATION=0
MODE_DESTRUCTION=0
TOMCAT_ENABLE_SHUTDOWN_PORT=""
OPTION_WORKERS_PROPERTIES=""
OPTION_VHOST_APACHE_SERVERNAME=""
NB_CHIFFRES_DANS_NUM_INSTANCE=$( printf "%d" "$MAX_INSTANCES" | wc -c )
REP_TOMCAT_MULTIINSTANCE="/var/lib/tomcat$VERSION_TOMCAT-multiinstances"
# On récupère également le nom de l'utilisateur et du groupe de Tomcat
if [ ! -f /etc/default/tomcat$VERSION_TOMCAT ]; then
	echo "ERREUR: /etc/default/tomcat$VERSION_TOMCAT inaccessible." >&2
	exit 1
fi
. /etc/default/tomcat$VERSION_TOMCAT

# Par défaut, on arrête le script à la première erreur non "catchée"
set -e

# Fonctions

# Arguments
# $1 : Version de Tomcat
# $2 : nom du worker
# $3 : port AJP du worker
ajout_worker_properties() {
	test -n "$1" && test -n "$2" && test -n "$3" || exit 1
	# On se fiche un peu de la version de Tomcat, en fait :)
	case "$1" in
		6|7)
			cat <<EOF

# Script MI Tomcat : ajout du worker $2
worker.$2.port = $3
worker.$2.host = localhost
worker.$2.type = ajp13
EOF
			;;
		*)
			echo "ERREUR: version de Tomcat non-gérée." >&2
			exit 1
			;;
	esac
}

# Arguments
# $1 : Version de Tomcat
# $2 : port système (d'habitude 8005)
# $3 : port connecteur HTTP (8080, d'habitude)
# $4 : port connecteur AJP (8009, d'habitude)
generate_patch_tomcat_server_xml() {
	test -n "$1" && test -n "$2" && test -n "$3" && test -n "$4" && test -n "$5" || exit 1
	case "$1" in
		6|7)
			generate_patch_tomcat_server_xml_6 "$2" "$3" "$4" "$5"
			;;
		*)
			echo "ERREUR: version de Tomcat non-gérée." >&2
			exit 1
			;;
	esac
}

# Arguments
# $1 : port système (d'habitude 8005)
# $2 : port connecteur HTTP (8080, d'habitude)
# $3 : port connecteur AJP (8009, d'habitude)
generate_patch_tomcat_server_xml_6() {
	local NOM_WORKER TOMCAT_PORT_SYS TOMCAT_PORT_HTTP TOMCAT_PORT_AJP 
	test -n "$1" && test -n "$2" && test -n "$3" && test -n "$4" || exit 1
	NOM_WORKER="$1"
	TOMCAT_PORT_SYS="$2"
	TOMCAT_PORT_HTTP="$3"
	TOMCAT_PORT_AJP="$4"
	cat <<EOF
--- server.xml
+++ server.xml
@@ -19,7 +19,7 @@
      define subcomponents such as "Valves" at this level.
      Documentation at /docs/config/server.html
  -->
-<Server port="8005" shutdown="SHUTDOWN">
+<Server port="$TOMCAT_PORT_SYS" shutdown="SHUTDOWN">
   <!-- Security listener. Documentation at /docs/config/listeners.html
   <Listener className="org.apache.catalina.security.SecurityListener" />
   -->
@@ -69,10 +69,12 @@
          APR (HTTP/AJP) Connector: /docs/apr.html
          Define a non-SSL HTTP/1.1 Connector on port 8080
     -->
-    <Connector port="8080" protocol="HTTP/1.1"
+    <!--
+    <Connector port="$TOMCAT_PORT_HTTP" protocol="HTTP/1.1"
                connectionTimeout="20000"
                URIEncoding="UTF-8"
                redirectPort="8443" />
+    -->
     <!-- A "Connector" using the shared thread pool-->
     <!--
     <Connector executor="tomcatThreadPool"
@@ -91,9 +93,7 @@
     -->
 
     <!-- Define an AJP 1.3 Connector on port 8009 -->
-    <!--
-    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
-    -->
+    <Connector port="$TOMCAT_PORT_AJP" protocol="AJP/1.3" redirectPort="8443" address="127.0.0.1" />
 
 
     <!-- An Engine represents the entry point (within Catalina) that processes
@@ -105,7 +105,7 @@
     <!-- You should set jvmRoute to support load-balancing via AJP ie :
     <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
     -->
-    <Engine name="Catalina" defaultHost="localhost">
+    <Engine name="Catalina" defaultHost="localhost" jvmRoute="$NOM_WORKER">
 
       <!--For clustering, please take a look at documentation at:
           /docs/cluster-howto.html  (simple how to)
EOF
}

# Petites fonctions pour éviter de faire des concatenations
# hasardeuses un peu partout
get_tomcat_user() {
	case $VERSION_TOMCAT in
		'6')
			printf "%s" "$TOMCAT6_USER"
			;;
		'7')
			printf "%s" "$TOMCAT7_USER"
			;;
	esac
}
get_tomcat_group() {
	# TODO : une idée à creuser serait de créer un groupe tomcat incluant tomcat6 et tomcat7
	case $VERSION_TOMCAT in
		'6')
			printf "%s" "$TOMCAT6_GROUP"
			;;
		'7')
			printf "%s" "$TOMCAT7_GROUP"
			;;
	esac
}

# affiche le message d'aide
usage() {
cat <<EOF
  $0 -c suffixe
  $0 -d numero_instance
  $0 -h

	-c xxxx :   création d'une instance avec le suffixe 'xxxx' (15 caract. max)
	-d NNN  :   destruction de l'instance numéro NNN

	-S      :   activation du port d'extinction de Tomcat
	 (déconseillé en production, ne gêne pas le gestionnaire de démon de Debian :
	 http://tomcat.apache.org/tomcat-7.0-doc/config/server.html#Common_Attributes
	 http://wiki.apache.org/tomcat/FAQ/Security#Q2 )


Ce script peut également modifier le fichier workers.properties
pour y ajouter les infos du worker créé.
	-w /etc/.../workers.properties : Modification du workers.properties

	-h :       ce message d'aide
EOF
}


# Début du code
# gestion des options de lancement
while getopts c:d:Sw:h f; do
	case $f in
		'c')
			MODE_CREATION=1
			SUFFIX="$OPTARG"

			OLD_LC_ALL="$LC_ALL"
			export LC_ALL=C
			export LANG=C
			if [ $( printf "$SUFFIX" | egrep -c "^[a-z][a-z0-9_]{0,14}$" ) -ne 1 ]; then
				echo "ERREUR: suffixe vide ou contenant des caractères interdits : regexp : [a-z][a-z0-9_]{0,14}" >&2
				exit 1
			fi
			export LC_ALL="$OLD_LC_ALL"
			export LANG="$OLD_LC_ALL"
			;;

		'd')
			MODE_DESTRUCTION=1
			NUM_INSTANCE=$( printf "%d" "$OPTARG" )
			;;

		'h')
			usage
			exit 0
			;;

		'S')
			TOMCAT_ENABLE_SHUTDOWN_PORT="1"
			;;

		'w')
			OPTION_WORKERS_PROPERTIES="$OPTARG"
			if [ ! -f "$OPTION_WORKERS_PROPERTIES" ]; then
				echo "ERREUR: fichier workers.properties inexistant : $OPTION_WORKERS_PROPERTIES" >&2
				exit 1
			fi
			;;

		\?)
			usage >&2
			exit 1
			;;
	esac
done
#(code inutile pour ce script, mais que je garde parce qu'on ne sait jamais)
#shift $( expr $OPTIND - 1 )
#DATA="$1"

# Petite vérif.
case $(( $MODE_CREATION + $MODE_DESTRUCTION )) in
	'0')
		echo "ERREUR: veuillez choisir entre création et destruction." >&2
		usage >&2
		exit 1
		;;
	'2')
		echo "ERREUR: tu veux créer et détruire en même temps, petit malin ?" >&2
		exit 1
		;;
esac


# - est-ce qu'on est bien sur une Debian 6 ou 7
#   (c'est surtout pour éviter les mauvaises surprises, ce script n'ayant pas été testé ailleurs)
if [ ! -f "/etc/debian_version" ] || [ $( egrep -c "^(6.|7.)" /etc/debian_version ) -ne 1 ]; then
	echo "ERREUR: mauvaise version de Debian détectée. Arrêt par prudence. Vérifiez le code de ce script avant de forcer." >&2
	exit 1
fi

# - est-ce que Tomcat est bien installé ?
if ! dpkg -l tomcat$VERSION_TOMCAT >/dev/null 2>&1; then
	echo "ERREUR: tomcat$VERSION_TOMCAT ne semble pas avoir été installé." >&2
	exit 1
fi

# - on vérifie que le /etc/default/tomcatN contient bien les infos qui nous intéresse
if [ ! -n $( get_tomcat_user ) ] || [ ! -n $( get_tomcat_group) ]; then
	echo "ERREUR: TOMCAT_USER ou TOMCAT_GROUP indeterminé." >&2
	exit 1
fi

# Fin des vérifications


if [ "$MODE_CREATION" -eq 1 ]; then
	# Détermination du numéro d'instance :
	# On cherche un numéro inutilisé ni par une instance Tomcat, ni par une instance Postgres
	for NUM_INSTANCE in $( seq 1 $MAX_INSTANCES ); do
		# Finalement, on base tout sur le nom de l'instance : ça
		# nous permet de retrouver facilement le répertoire de l'instance
		# lors de la suppression (le HOME de tomcat7 est sur CATALINA_HOME,
		# pas sur CATALINA_BASE :'(
		NOM_INSTANCE=$( printf "%s_%0$NB_CHIFFRES_DANS_NUM_INSTANCE""d_%s" "$( get_tomcat_user )" "$NUM_INSTANCE" "$SUFFIX" )
		USERID_TOMCAT=$(( $START_RANGE_TOMCATUID + $NUM_INSTANCE ))
		USERNAME_TOMCAT="$NOM_INSTANCE"
		SCRIPTNAME_TOMCAT="$NOM_INSTANCE"
		WORKERNAME_TOMCAT="worker_$NOM_INSTANCE"
		if [ ! -d "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE" ] && ! getent passwd "$USERID_TOMCAT" >/dev/null 2>&1 && ! getent passwd "$USERNAME_TOMCAT" >/dev/null 2>&1; then
			echo "INFO: Numéro d'instance libre trouvé : $NUM_INSTANCE"
			break
		fi
	done
	if [ "$NUM_INSTANCE" -eq "$MAX_INSTANCES" ]; then
		echo "ERREUR: plus de numéro d'instance disponible." >&2
		exit 1
	fi

	# Vérifier que le REP_TOMCAT_MULTIINSTANCE pré-existe.
	# TODO review : finalement pas une bonne idée de le faire créer par le script ?
	if [ ! -d "$REP_TOMCAT_MULTIINSTANCE" ]; then
		# (finalement, on le crée nous même...)
		echo "WARNING: le répertoire '$REP_TOMCAT_MULTIINSTANCE' n'existe pas (normal si première exécution). Création..."
		mkdir -p "$REP_TOMCAT_MULTIINSTANCE"
		chown -R "root:$( get_tomcat_group )" "$REP_TOMCAT_MULTIINSTANCE"
		chmod 750 "$REP_TOMCAT_MULTIINSTANCE"
	fi

	# Copie du script d'init et du /etc/default de Tomcat
	# Note : pour les scripts d'init, j'aurais aimé faire de simples liens symboliques vers
	#        un script générique. Malheureusement, ça pose de gros soucis pour LSB (insserv
	#        semble avoir les liens symboliques en horreur) et on aurait dû tout faire à la
	#        main, d'où plus d'inconvénients que d'avantages.
	echo "INFO: création de l'utilisateur système dédié..."
	useradd -u "$USERID_TOMCAT" -s /bin/false -g $( get_tomcat_group ) -d $( getent passwd $( get_tomcat_user ) | cut -d : -f 6 ) "$USERNAME_TOMCAT"
	echo "INFO: création du script de lancement..."
	cat /etc/init.d/tomcat$VERSION_TOMCAT | \
		sed "s/^\(#[[:space:]]*Provides[[:space:]]*:[[:space:]]*\)tomcat[0-9]\+[[:space:]]*$/\1$SCRIPTNAME_TOMCAT/" | \
		sed 's/^NAME=tomcat[0-9]\+$/NAME=$( basename "$0" | sed "s\/^[SK][0-9]\+\/\/" )/' \
		>/etc/init.d/$SCRIPTNAME_TOMCAT
	chmod +x /etc/init.d/$SCRIPTNAME_TOMCAT
	echo "INFO: création du fichier de paramétrage dans /etc/default..."
	cat /etc/default/tomcat$VERSION_TOMCAT | sed "s/^\(TOMCAT[0-9]\+_USER\)=tomcat[0-9]\+$/\1=$USERNAME_TOMCAT/" >/etc/default/$SCRIPTNAME_TOMCAT
	echo "# Script MI Tomcat : Spécification des CATALINA_HOME et CATALINA_BASE" >>/etc/default/$SCRIPTNAME_TOMCAT
	echo "CATALINA_HOME=/usr/share/tomcat$VERSION_TOMCAT" >>/etc/default/$SCRIPTNAME_TOMCAT
	echo "CATALINA_BASE=$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE" >>/etc/default/$SCRIPTNAME_TOMCAT

	# Activation du script d'init
	echo "INFO: activation du script d'init..."
	update-rc.d $SCRIPTNAME_TOMCAT defaults


	# Instanciation du CATALINA_BASE
	# Copie du /var/lib/tomcatN (notre template, en gros)
	echo "INFO: génération du CATALINA_BASE ( $REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE )..."
	mkdir "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE"
	cp -a /var/lib/tomcat$VERSION_TOMCAT/* "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/"
	rm "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/conf"
	rm "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/logs"
	rm "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/work"
	mkdir "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/conf"
	mkdir "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/logs"
	mkdir "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/work"
	cp -r /var/lib/tomcat$VERSION_TOMCAT/conf/* "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/conf/"
	# Parfois, le work est vide. Solution temporaire : on émet un warning
	if [ "$( ls /var/lib/tomcat$VERSION_TOMCAT/work/* 2>/dev/null | wc -l )" -gt 0 ]; then
		cp -r /var/lib/tomcat$VERSION_TOMCAT/work/* "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/work/"
	else
		echo "INFO: le répertoire /work/ du template est vide. A priori, c'est normal."
	fi
	chown -R $USERNAME_TOMCAT:$( get_tomcat_group ) "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE"

	# Réglage des ports TCP/IP
	generate_patch_tomcat_server_xml "$VERSION_TOMCAT" \
			"$WORKERNAME_TOMCAT" \
			$( test -n "$TOMCAT_ENABLE_SHUTDOWN_PORT" && echo $(( $START_RANGE_TOMCAT_PORT_SYS + $NUM_INSTANCE )) || echo "-1" ) \
			$(( $START_RANGE_TOMCAT_PORT_HTTP + $NUM_INSTANCE )) \
			$(( $START_RANGE_TOMCAT_PORT_AJP + $NUM_INSTANCE ))  | \
			patch $REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/conf/server.xml

	# Génération du fichier de config de logrotate
	echo "INFO: génération du logrotate..."
	cat /etc/logrotate.d/tomcat$VERSION_TOMCAT | \
		sed "s#^/var/log/tomcat[0-9]\+/catalina.out #$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE/logs/catalina.out #" | \
		sed "s/create 640 tomcat[0-9]\+ adm/create 640 $USERNAME_TOMCAT adm/" \
		>/etc/logrotate.d/$SCRIPTNAME_TOMCAT

	# partie annexe n°1 : workers.properties
	if [ -f "$OPTION_WORKERS_PROPERTIES" ]; then
		echo "INFO: Modification du workers.properties..."
		# Ajout du worker à la worker.list
		TMP_SUFFIX=$( date +%Y%m%d-%H%M%S )
		sed -i.$TMP_SUFFIX -e "s/^worker.list=\(.\+\)$/worker.list=\1,$WORKERNAME_TOMCAT/g" -e "s/^worker.list=$/worker.list=$WORKERNAME_TOMCAT/g" "$OPTION_WORKERS_PROPERTIES"
		echo "NOTICE: une sauvegarde $OPTION_WORKERS_PROPERTIES.$TMP_SUFFIX a été créée, n'hésitez pas à la supprimer."

		# Ajouts de la définition du worker
		ajout_worker_properties "$VERSION_TOMCAT" "$WORKERNAME_TOMCAT" $(( $START_RANGE_TOMCAT_PORT_AJP + $NUM_INSTANCE )) >> "$OPTION_WORKERS_PROPERTIES"
	fi

	# Fin
	echo "INFO: Création terminée. Serveur Tomcat éteint."
	echo "INFO: Pour connecter mod-jk au worker, voici la ligne à ajouter au vhost : JkMount /* $WORKERNAME_TOMCAT"
	echo "INFO: Pour allumer l'instance Tomcat : service $SCRIPTNAME_TOMCAT start"
fi

if [ "$MODE_DESTRUCTION" -eq 1 ]; then
	USERID_TOMCAT=$(( $START_RANGE_TOMCATUID + $NUM_INSTANCE ))

	#echo "NOTICE: le script est en mode -e. Il s'arrête donc à la première erreur. Le cas échéant, il faudra terminer le nettoyage à la main."

	# On recherche le nom de l'instance
	USERNAME_TOMCAT=$( getent passwd $USERID_TOMCAT | cut -d : -f 1 )
	NOM_INSTANCE="$USERNAME_TOMCAT"
	SCRIPTNAME_TOMCAT="$NOM_INSTANCE"
	WORKERNAME_TOMCAT="worker_$NOM_INSTANCE"

	if [ ! -n "$USERNAME_TOMCAT" ]; then
		echo "ERREUR: l'utilisateur correspondant à l'uid $USERID_TOMCAT est introuvable." >&2
		exit 1
	fi
	if service "$SCRIPTNAME_TOMCAT" status >/dev/null 2>&1; then
		echo "ERREUR: l'instance tomcat tourne toujours. Ne pas oublier le service $SCRIPTNAME_TOMCAT stop" >&2
		exit 1
	fi

	# Suppression utilisateur et répertoire dédié
	echo "INFO: suppression de l'utilisateur..."
	userdel $USERNAME_TOMCAT
	echo "INFO: suppression du répertoire dédié..."
	rm -rf "$REP_TOMCAT_MULTIINSTANCE/$NOM_INSTANCE"
	echo "INFO: suppresion fichier default..."
	rm -f "/etc/default/$SCRIPTNAME_TOMCAT"

	# Suppression du script d'init
	echo "INFO: désactivation et suppression du script d'init..."
	rm -f /etc/init.d/$SCRIPTNAME_TOMCAT
	update-rc.d "$SCRIPTNAME_TOMCAT" remove

	# Suppression logrotate
	echo "INFO: suppression logrotate..."
	rm -f /etc/logrotate.d/$SCRIPTNAME_TOMCAT

	# partie annexe n°1 : workers.properties
	if [ -f "$OPTION_WORKERS_PROPERTIES" ]; then
		echo "INFO: Modification du workers.properties..."
		# Soustraction de la définition du worker
		TMP_SUFFIX=$( date +%Y%m%d-%H%M%S )
		sed -i.$TMP_SUFFIX -e "/^worker.list=/s/$WORKERNAME_TOMCAT[,]\?//g" \
				-e '/^worker.list=.*,$/s/,$//' \
				-e "/^# Script MI Tomcat : ajout du worker $WORKERNAME_TOMCAT$/d" \
				-e "/^worker.$WORKERNAME_TOMCAT/d" \
				"$OPTION_WORKERS_PROPERTIES"
		echo "NOTICE: une sauvegarde $OPTION_WORKERS_PROPERTIES.$TMP_SUFFIX a été créée, n'hésitez pas à la supprimer."
	fi

	# Fin
	echo "INFO: suppression terminée."
fi