Apagar automáticamente thinclients al apagar, reiniciar o cerrar sesión en el servidor LTSP | Algo de Linux

miércoles, 22 de mayo de 2013

Apagar automáticamente thinclients al apagar, reiniciar o cerrar sesión en el servidor LTSP

Aprovechando que recientemente me han preguntado sobre el tema, voy a escribir esta entrada en la que os voy a mostrar un script muy interesante que creé allá por el año 2011 para apagar los thinclients cuando un usuario apaga el servidor LTSP, lo reinicia o cierra la sesión.

Uno de los problemas de LTSP (Linux Terminal Server Project) es que confía en que el usuario apague los terminales, antes de apagar el servidor, y si no lo hace, los terminales quedarán bloqueados y habrá que apagarlos pulsando el botón de apagado de cada uno de ellos o bajando los diferenciales y/o magnetotérmicos para cortar la corriente. 

Lo primero puede llegar a ser realmente pesado, sobre todo si tienes cada equipo en un cajón cerrado. Lo segundo no es muy recomendable.

Este script está al alcance de todos los administradores, puesto que en su día subí a desarrollo un módulo puppet para montarlo en los servidores de terminales del centro. En cualquier caso, por si alguien más lo quiere, cuelgo aquí también el módulo:
A continuación podéis ver el init.pp del módulo que lo instala vía puppet:

class apagado-terminales {

   file { 
      "/usr/sbin/apagaterminales.sh" :
      source => "puppet://puppetinstituto/apagado-terminales/apagaterminales.sh",
      owner => root, group => root, mode => 750
   }

   file {
      "/opt/ltsp/i386/root/.ssh" :
      ensure => directory, owner => root,  group => root, mode => 700,
   }

   file { "/root/.ssh/id_rsa.pub":
           ensure => file, owner => "root", group => "root", mode => "644",
   }

   file {
      "/root/.ssh/known_hosts" :
      ensure => file, owner => root,  group => root, mode => 644,
   }

   exec { "/usr/bin/ssh-keygen -q -t rsa -N '' -f /root/.ssh/id_rsa":
        unless => "/usr/bin/test -f /root/.ssh/id_rsa",
        notify => Exec["copy-publickey"]
   }

   exec { "copy-publickey":
        command => "/bin/cat /root/.ssh/id_rsa.pub >> /opt/ltsp/i386/root/.ssh/authorized_keys",
 require => [File["/root/.ssh/id_rsa.pub"], File["/opt/ltsp/i386/root/.ssh"]],
        unless => '/bin/grep "$fqdn" /opt/ltsp/i386/root/.ssh/authorized_keys 2>/dev/null',
        notify => Exec["update-image"]
   }

   exec { "update-image":
        command => "/usr/sbin/ltsp-update-image --arch i386",
        path => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        refreshonly => true
       }

   exec { '/bin/sed -i -e "s/exit 0/\/usr\/sbin\/apagaterminales.sh \&\n\nexit 0/g" /etc/gdm3/PostSession/Default':
        unless => '/bin/grep "apagaterminales" /etc/gdm3/PostSession/Default 2>/dev/null'
   }

   file {
      "/etc/init.d/halt-terminales" :
      source => "puppet://puppetinstituto/apagado-terminales/halt-terminales",
      owner => root, group => root, mode => 750
   }

   exec { "/bin/ln -sf /etc/init.d/halt-terminales /etc/rc0.d/K01aapaga-terminales":
      unless => "/usr/bin/test -L /etc/rc0.d/K01aapaga-terminales"
   }

   exec { "/bin/ln -sf /etc/init.d/halt-terminales /etc/rc6.d/K01aapaga-terminales":
      unless => "/usr/bin/test -L /etc/rc6.d/K01aapaga-terminales"
   }

}

Si os fijáis, el módulo es muy completito:
  • Crea un par de claves pública/privada, copia la clave pública al usuario root del chroot de los terminales y regenera la imagen para los terminales (usamos nbd).
  • Copia el script apagaterminales.sh al directorio /usr/sbin del servidor de terminales.
  • Coloca un script de apagado de los terminales en /etc/init.d/halt-terminales y crea los enlaces correspondientes en /etc/rc0.d y /etc/rc6.d para realizar el procedimiento de apagado cuando el usuario apague el servidor o lo reinicie.
  • Y, además, modifica el archivo /etc/gdm3/PostSession/Default para apagar también los terminales cuando el usuario cierre sesión.
Por otra parte, aquí tenéis el script que realiza el apagado de los terminales /usr/sbin/apagaterminales.sh:
#!/bin/bash
# Esteban M. Navas
# IES Valle del Jerte - Plasencia
# 03/02/2011
# Última modificación: 27/11/2013

# apagaterminales.sh -> Apaga los terminales de un aula
# El script hace uso de avahi-browse y ssh-keyscan para cumplir con su función.
# Está pensado para ejecutar el comando de apagado tan sólo sobre las máquinas que se detecten encendidas
# pero al mismo tiempo, va construyendo una lista con las ips de las máquinas que se van detectando.
# Hace uso de ssh-keyscan para obtener las claves rsa de los terminales encendidos y añadirlas al fichero 
# /root/.ssh/known_hosts del servidor de terminales.
# Borra los temporales de los usuarios que iniciaron sesión en los terminales y mata sus procesos

# Detectamos los terminales encendidos, tal y como está definido en ldap
avahi-browse -trpk -d local _workstation._tcp 2>/dev/null | grep 192.168.0. | grep -v '_pro\|\-pro'| cut -d";" -f8 > /tmp/terminalesUp

# Obtenemos las claves rsa de los terminales encendidos
ssh-keyscan -t rsa -f /tmp/terminalesUp > /tmp/rsaterminalesUp

# Añadimos las claves rsa de los terminales encendidos al fichero known_host de root del servidor de terminales
sort -o /root/.ssh/known_hosts -m /tmp/rsaterminalesUp /root/.ssh/known_hosts
sort -o /root/.ssh/known_hosts -u /root/.ssh/known_hosts

# Apagamos los terminales 
while read IP
do
   ssh root@$IP -a /sbin/poweroff -fp &
done < /tmp/terminalesUp

# Obtenemos la lista de usuarios que han iniciado sesión en terminales
w | grep '192.168.0' > /tmp/userstoclean

# Borramos temporales y matamos procesos de aquellos usuarios que hayan iniciado sesión en un terminal
while read SESION ; do

   USUARIO=`echo $SESION | cut -f1 -d" "`

   # Borramos los temporales creados en /tmp al iniciar sesión el usuario 
   find /tmp -not -user root -user $USUARIO -exec rm -r {} \; 2>/dev/null

   # Matamos todos los procesos del usuario
   pkill -9 -u $USUARIO
done < /tmp/userstoclean

rm /tmp/userstoclean

Está super-comentado para que cualquiera que lo lea entienda lo que hace.

Si lo leéis detenidamente, veréis que es un script muy potente:
  • Detecta los terminales que hay encendidos en el momento de ejecutar el script.
  • Obtiene las claves rsa de dichos terminales para que, a la hora de ejecutar las conexiones ssh, no se quede esperando respuesta por parte del usuario, y, además, va construyendo la lista de terminales conocidos en cada ejecución, evitando que haya claves repetidas en el archivo known_hosts
  • Apaga los terminales rápidamente.
  • Y, además, hace limpieza borrando los archivos temporales que se crean en el directorio /tmp del servidor de terminales por cada usuario que inicia sesión en un terminal. Esto es importante.
Y, por último, podéis ver el script de inicio normalizado /etc/init.d/halt-terminales:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          halt-terminales
# Required-Start:
# Required-Stop:
# Default-Start:
# Default-Stop:      0 6
# Short-Description: Execute the reboot command.
# Description:
### END INIT INFO

PATH=/sbin:/usr/sbin:/bin:/usr/bin

. /lib/lsb/init-functions

do_stop () {
 # Apagamos los terminales
 /usr/sbin/apagaterminales.sh &
}

case "$1" in
  start)
 # No-op
 ;;
  restart|reload|force-reload)
 echo "Error: argument '$1' not supported" >&2
 exit 3
 ;;
  stop)
 do_stop
 ;;
  *)
 echo "Usage: $0 start|stop" >&2
 exit 3
 ;;
esac