screen:: algunas notas útiles II


"Screen is a full-screen window  manager  that multiplexes a physical terminal between several processes (typically interactive shells).  Each virtual terminal provides the functions of a DEC VT100 terminal and, in addition, several  control  functions  from the  ISO  6429  (ECMA  48,  ANSI  X3.64) and ISO 2022 standards (e.g. insert/delete line and support for multiple character sets)."

Cómo puedo usar Screen?
Retomando el primer post acerca de esta herramienta, pues vamos a darle un uso y será con un pequeño script en shell básico.

Vamos a plantear el siguiente escenario en un departamento de sistemas con dos administradores de sistemas, que se enfrentan al problema de estar iniciando aplicaciones de la mas diversa indole y con instrucciones distintas para cada una de ellas. Se han planteado el hecho de desarrollar un script para cada aplicación, lo cual con el tiempo se ha convertido en una actividad absorvente y propensa a errores ó arrancar las aplicaciones de la manera indicada por el área solicitante; obviamente lo cual les llevará mucho tiempo realizarlo.

Es entonces que deciden desarrollar la primera idea, pero cambiando el esquema; tener un archivo de configuración donde puedan indicar como ejecutar el programa, es innegable el hecho de pensar en multiples opciones: pero necesitaban algo que fuera sencillo de usar e implementar.

Uno de ellos, Gentoo Linux fan lover, ahora por aras del destino administrador en Unix (si pensaban que la consola del powershell de WindowsVista era un infierno, entonces no han trabajado con HP-UX!) recordo la manera en como Gentoo trabajaba los scripts de arranque para las aplicaciones y pensó, "bueno, podemos hacer algo pequeño para estos unix.. veamos!"

El esquema a grandes rasgos el siguiente:

nada del otro mundo, estamos de acuerdo!

Ahora bien, el script es el siguiente:
#!/bin/sh 
# vim: set ts=2 sw=2 sts=2 et si ai: 

# starter.sh -- easy start / stop applications for dummies
# ----------------------------------------------------------------------------
# (c) 2009 StrategiesLabs!
# Andres Aquino 
# All rights reserved.
# 

# get application Name and Action
apAppName="starter"
apHome=${HOME}/starter
apDir=`dirname ${PWD}/${0}`
apName=`basename ${0%.*}`
scrPrcs=0

# move to application home directory
cd ${apHome}

# i need a config file...
[ ! -e ${apHome}/starter.conf ] && echo "hey!, i need a config file like starter.conf" && exit 1
[ ! -e ${apHome}/setup/${apName}.conf ] && echo "hey!, i need a config file like ${apHome}/setup/${apName}.conf" && exit 1

# settings, setup & libraries
. ${apHome}/starter.conf
. ${apHome}/libutils.sh
. ${apHome}/setup/${apName}.conf

#
get_enviroment
apAction=`basename ${0#*.} | tr "[:upper:]" "[:lower:]"`
apHost=`hostname | tr "[:upper:]" "[:lower:]" | sed -e "s/m.*hp//g"`
apLog="${apHome}/log/${apName}"

# virtual terminal name
scrPrcs=`echo ${apName} | sed -e "s/[a-zA-Z\.]/0/g;s/.*\([0-9][0-9]\)$/\1/g"`
scrName=`echo "$(echo "${apHost}____" | cut -c 1-4)" | tr "[:lower:]" "[:upper:]"`
scrName="${scrName}${apType}${scrPrcs}"

get_enviroment 
pidfile="/tmp/${0%.*}.pid"

##
executeCmd () {
   local strCommand=${1}

   # eliminar referencias nulas del screen
   screen -wipe > /dev/null 2&>1

   # si no hay otro proceso
   screen -ls | grep ${scrName} > /dev/null 2>&1
   [ "x$?" != "x0" ] && log_action "ERR" "${scrName} virtual terminal process doesn't exist!" && exit 1 

   # ejecutar el comando
   screen -x ${scrName} -p 0 -X stuff "$(printf '%b' "${strCommand}\015")"
   wait_for "Executing command ${strCommand} on ${scrName} " 2

   # reportar el estado de la ejecucion
   log_action "INFO" "${strCommand} on ${scrName} cooked, go home baby! "
 
}

##
get_tree_of_applications () {
   local nameProcess=${1}

   rm -f $pidfile
   get_process_id "$nameProcess"

   [ ! -e $pidfile ] && log_action "ERR" "${scrName} process doesn't exist!" && exit 1 

   p=`cat $pidfile | sort -n | head -n1`
   echo $p > /tmp/${scrName}.proc
   while true
   do
      case "${systemSO}" in
         "HP-UX")
            proc=`cat /tmp/pslist | awk -v pp=$p '{if ($5 ~ pp){print $4}}' 2> /dev/null | sed -e "s/ *//g"`
            ;;
         "Linux")
            proc=`cat /tmp/pslist | awk -v pp=$p '{if ($4 ~ pp){print $3}}' 2> /dev/null | sed -e "s/ *//g"`
            ;;
      esac
      
      [ "x$proc" = "x" ] && break
      
      p=$proc
      echo $proc >> /tmp/${nameProcess}.proc
   done
   sort -n /tmp/${nameProcess}.proc > /tmp/${nameProcess}.list

}


# START
if [ ${apAction} = "start" ]
then

   # eliminar referencias nulas del screen
   screen -wipe > /dev/null 2>&1

   screen -ls | grep ${scrName} > /dev/null 2>&1
   [ "x$?" = "x0" ] && log_action "ERR" "Another ${scrName} virtual terminal process exist!" && exit 1 
   
   # backup
   mkdir -p ${apHome}/log/${apDate}
   mv ${apLog}.log ${apHome}/log/${apDate}/${scrName}.log.`date '+%H%M'` > /dev/null 2>&1

   #
   . ${apHome}/setup/${apName}.conf
   screen -d -m -S ${scrName}
   screen -r ${scrName} -p 0 -X log off
   screen -r ${scrName} -p 0 -X logfile ${apLog}.log
   screen -r ${scrName} -p 0 -X logfile flush 10
   screen -r ${scrName} -p 0 -X log on
   wait_for "Starting ${scrName} virtual terminal " 6
   
   #
   screen -r ${scrName} -p 0 -X stuff "$(printf '%b' "${apCommand}\015")"
   wait_for "Starting process " 8
   log_action "INFO" "Process ${apType} running in background "

   # get the tree applications
   get_tree_of_applications ${scrName}

   exit 0

fi

# CHECK
if [ ${apAction} = "check" ]
then

   # determinar procesos a terminar
   get_tree_of_applications ${scrName}

   #
   echo "Execution's tree"
   pos=
   for PID in $(cat /tmp/${scrName}.list)
   do
       case "${systemSO}" in
         "HP-UX")
            pname=`cat /tmp/pslist | awk -v pp=$PID '{if ($4 ~ pp){print $0}}' | sed -e "s/.*[0-9]:[0-9][0-9]//g" 2> /dev/null` 
            ;;
         "Linux")
            pname=`cat /tmp/pslist | awk -v pp=$PID '{if ($3 ~ pp){print $0}}' | sed -e "s/.*[0-9]:[0-9][0-9]//g" 2> /dev/null`
            ;;
      esac
      echo "${pos} '- (${PID})${pname}"
      [ "x${pos}" = "x" ] && pos=" "
   done

fi

 
# STOP
if [ ${apAction} = "stop" ]
then

   # determinar procesos a terminar
   get_tree_of_applications ${scrName}

   # si no hay otro proceso
   screen -ls | grep ${scrName} > /dev/null 2>&1
   if [ "x$?" != "x0" ] 
   then
      log_action "ERR" "OMFG...! Nothing to stop man. "
      exit 1
   else
      screen -r ${scrName} -p 0 -X log off
      screen -r ${scrName} -p 0 -X stuff "$(printf '%b' "exit\015")"
      wait_for "Stoping process " 14
      
      awk '{print "kill -9 "$1}' /tmp/${scrName}.list | sh > /dev/null 2>&1
      log_action "INFO" "Process ${scrName} finalized "
   fi
   exit 0

fi

# TODO
# cualquier otro comando ...
# app.backUp | app.logsClear 
case ${apAction}  in
   backup)
      #executeCmd "${apBackUp}"
      exit 0
      ;;

   logsclear)
      #executeCmd "${apLogsClear}"
      exit 0
      ;;

   version)
      #
      # generar num de release en base al changelog, para no modificar el md5 del script
      # sh ../playground/changelog.sh -> git log > CHANGELOG
      VERSIONAPP="1"
      UPVERSION=`echo ${VERSIONAPP} | sed -e "s/..$//g"`
      RLVERSION=`awk '/200/{t=substr($2,6,7);gsub("-",".",t);print t}' ${apHome}/CHANGELOG | head -n1`
      echo "${apAppName} v${UPVERSION}.${RLVERSION}"
      echo "(c) 2009 StrategiesLabs!"
      
      if [ "${TTYTYPE}" = "CONSOLE" ]
      then 
         echo "\nWritten by"
         echo "Andres Aquino "
      fi   
      exit 0
      ;;

esac

#
--

Observese las lineas donde se ejecuta el comando screen, lo recuerdan? Bueno, este script al cual hemos llamado starter, funciona de la siguiente manera, usando un application como ejemplo:
  1. Se ejecuta un application.start
  2. El application.start manda a llamar application.conf, aqui se especifica que comando se va a ejecutar realmente
  3. El application.start entonces carga una terminal virtual, establece un nombre y ciertos parámetros para preparar la ejecución del comando
  4. El application.start entonces ejecuta el comando en la terminal virtual y se desconecta, al final verifica el estado de la consola para saber si la ejecución se dio de manera correcta.
Simple!

Obviamente el stop funciona de una manera muy similar, solo que ahora si el comando no se termina de manera correcta, entonces se aplica una señal SIGKILL (9) para terminar la aplicación.

Como veras, fue muy sencillo implementarlo y de paso nos ahorramos ciertos comandos como el nohup. Y además podremos siempre verificar el estado de la ejecución de la aplicación consultando el log del mismo starter.

Si te interesa probar la aplicación, acá encuentras el repositorio. Bienvenidas sean las contribuciones y comentarios!

Corolario:
Al final, la herramienta resulto ser bastante útil, no solo por el hecho de su realización sino que le permitió a esos administradores tener más tiempo para resolver otros problemas,y por que no; irse por unas chelas!


res, non verba!