"Después del juego es antes del juego"
Sepp Herberger

martes, 30 de enero de 2018

Instalación de una aplicación mediante wine para todos los usuarios.

Hace poco me pidieron instalar la aplicación PhotoFiltre en Ubuntu ya que necesitaban usar una aplicación sencilla de retoque fotográfico. Solo tiene versión Windows, pero tras descargar el instalador y lanzarlo con wine confirmé que era totalmente funcional.

El problema que tiene wine es que la instalación de las aplicaciones va asociada al usuario, lo cual me obligaría a instalar la misma aplicación una y otra vez en todas las cuentas necesarias. En este caso la única molestia es el tiempo perdido apretando una y otra vez la misma tuerca, pero en aplicaciones que ocupan mucho espacio tenemos además el problema del disco desperdiciado con instalaciones idénticas en el home de cada alumno/profesor.

Según recuerdo con Codeweavers Crossover podías hacer una sola instalación de una aplicación Windows en una bottle (wine->bottle, son unos cachondos) y reusar luego esa bottle en distintas cuentas de usuario. Pero Crossover es de pago y yo tengo que usar wine de forma gratuita.

Así que buscando una manera de hacer una única instalación por máquina encuentro ésto y pruebo a ponerlo en práctica:

Creamos un usuario "windows" local con home en /var/home (para no interferir con nuestro /home en NFS):
# adduser --home /var/home/windows windows
Hago login con el nuevo usuario, descargo el PhotoFiltre y los ficheros de idioma español y los instalo con:
$ wine pf7-setup-en-7.2.1.exe
Después instalo el idioma español copiando el fichero según comentan en la página del programa.

El programa se ha instalado como es normal en wine, en la ruta:
/var/home/windows/.wine/drive_c/Program\ Files\ \(x86\)/PhotoFiltre\ 7/
habiéndose creado un acceso directo en el escritorio del usuario "windows". Si lo lanzamos desde dicho acceso directo se abre y funciona sin problema.

El reto está en hacer que funcione para los otros usuarios de la máquina sin tener que volver a instalarlo. Para ellos vamos a hacer uso de "sudo", que nos permitirá adoptar la identidad del usuario "windows" y ejecutar el programa desde otras sesiones de usuario. Empezamos tocando en el fichero sudoers:
# cat /etc/sudoers
..........
..........
# Configuracion Wine:
User_Alias WINDOWS_USERS = %teachers
Cmnd_Alias WINDOWS = /usr/bin/wine,/usr/bin/winecfg,/var/home/windows/pf7

Defaults:WINDOWS_USERS env_reset
Defaults:WINDOWS_USERS env_keep += DISPLAY
Defaults:WINDOWS_USERS env_keep += XAUTHORITY

WINDOWS_USERS ALL = (windows) NOPASSWD: WINDOWS

En este sudoers damos permisos a todos los usuarios del grupo "teachers" (aquí especificaremos los usuarios o grupo de usuario que queramos) a ejecutar wine, winecfg y el script /var/home/windows/pf7 con la identidad del usuario "windows". El contenido del script pf7 sería:
# cat /var/home/windows/pf7
#!/bin/bash
env WINEPREFIX="/var/home/windows/.wine" wine C:\\Program\ Files\ \(x86\)\\PhotoFiltre\ 7\\PhotoFiltre7.exe
exit 0
Lo que hace es fijar un WINEPREFIX en el directorio donde está wine y lanzar el ejecutable. Esta ruta está sacada del interior archivo .desktop que comentamoss anteriormente, creado por la aplicación en el escritorio durante su instalación con wine.

Ahora tenemos que establecer el medio para que los otros usuarios arranquen el script /var/home/windows/pf7 con la identidad del usuario "windows". Para ello definimos un nuevo script en una carpeta accesible para todo el mundo:
# cat /usr/bin/pf7
#!/bin/bash
xhost +
sudo -u windows -H /var/home/windows/pf7
exit 0
En él ponemos "xhost +" para permitir al usuario "windows" abrir aplicaciones dentro de nuestra sesión Xorg y hacemos el "sudo .../var/home/windows/pf7" para ejecutar el programa con la identidad "windows" sin que nos pida contraseña alguna.

Por último nos hace falta un fichero .desktop para poner en el escritorio de los usuario que vayan a ejecutar la aplicación:
# cat PhotoFiltre7.desktop
[Desktop Entry]
Name=PhotoFiltre 7
Exec=/usr/bin/pf7
Type=Application
StartupNotify=true
Path=/usr/bin
Icon=/usr/share/icons/hicolor/pf7.png
El icono /usr/share/icons/hicolor/pf7.png lo he extraido del .EXE con la aplicacion wrestool:
# wrestool -x fichero.exe -o .
Ultimando las cosas hemos creado una carpeta /wine con propietario "windows" y mapeado z: y "Mis Documentos" sobre ella usando winecfg con la cuenta "windows". De esta manera la aplicación podrá guardar sus datos en una carpeta accesible y fácil de encontrar.



Para finalizar, bloqueamos el login en la cuenta "windows", para que nadie trastee con ella y solo esté accesible a través de sudo:
# passwd -l windows
Y con esto ya tenemos hecha la tarea. A modo de recapitulación las ventajas son:
  • Realizamos una sola instalación y configuración por máquina, ahorrando espacio y trabajo.
  • Si la aplicación se estropea o deja de funcionar (en aplicaciones wine no es infrecuente, ya que el usuario tiene acceso total a la carpeta Archivos de Programa) no tenemos nada más que hacer una copia previa del directorio .wine con tar.gz y descomprimirla para dejar todo otra vez funcionando para todos los usuarios.
Y los inconvenientes:
  • Todos los usuarios de un mismo PC comparten la misma aplicación y carpeta de datos en /wine.
  • La aplicación va vinculada a la máquina y no al usuario. Si se mueve a otro PC no tiene acceso a sus datos o incluso puede no tener acceso a la aplicación
Por tanto, corresponde a cada cual valorar si merece o no la pena hacerlo así o de la forma tradicional.

Vamos que nos vamos.



lunes, 15 de enero de 2018

Poner las aplicaciones de Manjaro en español después una instalación fresca.

Después de instalar nuestro Manjaro XFCE en el primer arranque nos pide que instalemos los paquetes de idioma. Con las ansias de empezar a enredar lo dejamos para mas tarde y cerramos la ventana...de tal forma que luego no lo vuelve a pedir más veces y nos encontramos con que Firefox, Libreoffice y otras cosas están en inglis.

¿Cómo instalamos los paquetes de idiomas perdidos? Podemos ir al Gestor de Paquetes y buscarlos uno a uno, pero Manjaro nos tiene un sistema mejor para hacerlo de forma centralizada y ordenada.

Nos vamos al Gestor de Configuración de Manjaro en el menú:


Ahí elegimos "Configuración de idioma":


En esta ventana podemos ver los paquetes de idiomas instalados y los disponibles:


Con el botón de "Instalar paquetes" podemos elegir nuevos paquetes para añadir a las distintas aplicaciones:


Y ya está. Sencillo e indoloro, como todo en Manjaro.

domingo, 14 de enero de 2018

Como meter un recovery en Android desde linea de comandos.


Un dispositivo android tiene varios modos de arranque, desde un modo "mayormente muerto" (como el Pirata Roberts de La princesa prometida) hasta el modo completamente funcional.


Un modo muy usado si queremos trastear con el móvil es el "modo recovery", pero para sacarle toda su potencialidad normalmente nos vemos obligados a sustituir el recovery que trae por defecto el terminal por otro con más opciones. Aquí describiremos como meter un recovery nuevo mas potente a un dispositivo android.

Antes de entrar en faena y a modo de repaso para que no se me olvide, voy a enumerar los modos de los cuales tenemos noticia:

1. Modo normal/system.

Es el modo usual, con el dispositivo arrancado y funcional. Podemos interactuar con el mediante la pantalla táctil o mediante adb, conectándolo a un PC con el cable USB y enviando comandos hacia él.

El adb es además la vía mas sencilla y universal para reiniciar el móvil en los otros modos.

2. Modo recovery.:

En este modo el dispositivo arranca en un entorno restringido, que permite hacer varias tareas de mantenimiento que nos facilitan enredar con el dispositivo. Aquí el móvil se maneja con la pantalla táctil y/o los botones laterales y no es funcional en el sentido usual, ya que no se llega a cargar el sistema android.

Muchos móviles traen un stock recovery por defecto muy limitado, pero afortunadamente la comunidad ha creado recoverys sustitutos bastante avanzados, como CWM o TWRP.

Stock recovery, como vemos bastante insulso:


Recovery CWM, aquí hay mas opciones:


Recovery TWRP, con un GUI bonito y encima táctil:


Algunas de las funcionalidades que traen estos recoverys vitaminados son:

  • Cargar nuevas ROMs.
  • Hacer un reset de las distintas particiones y datos.
  • Instalar aplicaciones especiales, tipo SuperSU (para rootear) o Xposed (para añadir funcionalidades variadas).
  • Formateo, borrado, creación y montaje de particiones de la memoria SD interna del móvil.

Aquí y acá viene mejor explicado.

Para arrancar el móvil en recoverý suele haber una combinación especial de teclas de encendido o bien usando adb, como podemos ver aquí.

3. Modo fastboot/bootloader.

En este modo el móvil no puede ser manejado por su pantalla o botones, pero si podemos interactuar con él usando un cable USB y la utilidad fastboot.

Muchas veces tenemos un móvil brickeado (se queda congelando arrancando) o en bootloop (reinicia continuamente sin cargar el sistema). En estos casos si conseguimos ponerlo en modo fastboot tendremos una vía para restaurar o arreglar el sistema.

Normalmente se entra desde adb con el comando "adb reboot bootloader", aunque algunos dispositivos tienen combinaciones de teclas para arrancar en este modo. Es algo que tendremos que investigar en cada caso concreto.

Desde este modo se puede desbloquear el bootloader (en algunos móviles viene bloqueado para impedir instalar otras ROMs o recoverys) y trabajar con las particiones: borrar, formatear, sobreescribir imágenes nuevas. Es el modo que vamos a usar para meter un recovery más adelante.

4. Más abajo: modos cercanos al Amenti.

Aquí llegamos a terreno envuelto en brumas. Cada fabricante puede tener modos más profundos para arrancar el móvil, según estas normas básicas:
  • La nomenclatura depende del fabricante: download mode (aunque en algunos fabricantes es equivalente al fastboot), emergency mode, QDL mode, y seguro que hay muchos más.
  • En cada caso se usa herramientas especializadas, como Odin, SPFlashTool, MTKDroidTools, KDZ Firmware Update, MiFlash,...
  • Desde estos modos normalmente sólo se consigue flashear el móvil cargando una Stock ROM (ROM Oficial)
Cuando uno llega aquí es que tiene el móvil tan brickeado que no podemos conectar con él mediante fastboot y ya estamos en modo pánico. Mis tribulaciones con los malditos Xiami Redmi 1S son una prueba de ello.


Vamos al lío.

Yo he venido aquí a hablar de mi recovery, así que vamos a ello. Mi problema es que cada vez que tengo que meter un recovery me lanzo una y otra vez por el camino mas pedregoso: bajarme los drivers USB el teléfono, instalar una utilidad de flasheo, conseguir el fichero con el recovery para mi modelo de móvil (no vale cualquiera, hay que buscar el correcto en htcmania/xda-developers o generarlo nosotros) y realizar el flasheo de la partición que lo aloja.

Esa es la teoría. En la práctica, como la mayoría de estas utilidades solo funcionan bien en Windows no queda otra que arrancar dicho sistema, sortear los problemas con los drivers no firmados (la mayoría de los drivers de móviles están sin firmar), ver si detecta o no el dispositivo USB, ver si hay comunicación estable... y un sinfín de contratiempos más. Después de un par de horas infructuosas recuerdo que hay un método mucho más sencillo usando la línea de comandos de linux:

  1. Localizar y descargar el recovery. Suele ser un fichero .img que contiene dentro imagen de una partición con un mini-sistema Linux
  2. Conectar por adb y arrancar en modo fastboot.
  3. Desbloquear el bootloader, cargar el recovery y reiniciar.

Veamos la secuencia de comandos. Antes de nada debemos instalar las android-tools-adb y android-tools-fastboot en nuestro Linux, a veces vienen por separado o a veces en un único paquete android-tools. No hace falta ningún driver. También hay que habilitar el modo depuración USB en el menú de desarrollo del móvil.

Conectamos el móvil al PC por usb y tecleamos:
# adb devices
Si todo va bien saldrán numeritos y el nombre del teléfono. Ojo: quizá el móvil nos pregunte ahora o más adelante en su pantalla si damos permisos para aceptar la conexión desde el PC.

Reiniciamos el móvil en modo fastboot:
# adb reboot bootloader
En ese modo la pantalla no muestra nada interesante, la única manera de interactuar es a través del comando fastboot en el PC. Vemos si hay comunicación con dicho modo desde el ordenador tecleando:
# fastboot devices
Antes de nada, puede que el terminal tenga el bootloader bloqueado. Eso quiere decir que viene de fábrica configurado para impedir cambiar el recovery y/u otras partes del sistema. En ese caso lo que haremos será desloquearlo con uno de estos 2 comandos:
# fastboot oem unlock
o
# fastboot oem unlock-go
Podemos ver el estado del bootloader con:
# fastboot oem device-info
y/o
# fastboot getvar all
Debe aparecer algo así como: "Device Unlocked: true".

Una vez confirmamos que el móvil está desbloqueado, procedemos a sustituir el revovery por uno nuevo descargado de Internet (recordemos: el recovery debe ser compatible con nuestro terminal, no se pueden hacer mezclas a riesgo de brickear el movil).
# fastboot flash recovery twrp.img
También es posible hacer una prueba del recovery en dique seco, sin reescribir el original. Así confirmamos su buen funcionamiento/compatibilidad o bien lo ejecutamos para algo puntual sin sustituir el recovery que hay instalado:
# fastboot boot twrp.img
Una vez sobreescrito el nuevo recovery, reiniciamos:
# fastboot reboot
El móvil debe reiniciar bien y una vez arrancado, desde el adb podemos reiniciar en el recovery para operar con él:
# adb devices
# adb reboot recovery
Y ya está, espero que la próxima vez no tenga que perder el tiempo con Windows cuando quiera hacer esto.

martes, 5 de diciembre de 2017

Instalar un panel informativo en el IES.

A los centros eScholarium se enviaron unos paneles informativos para poner en sitios visibles y poder publicar contenidos de interés para la comunidad educativa. Por desgracia, mi centro no estaba entonces en ese programa y no nos llegó nada.

Pero por fortuna teníamos una antigua TV plana (en realidad relativamente plana, es de las primeras que salieron y tiene un grosor de mas de 10cm) que estaba ociosa después de instalar cañones de vídeo en casi todas las aulas, y pensamos en rescatarla para hacer nuestro propio panel informativo. Como no es una Smart TV, para darle la parte smart hace falta un elemento externo, que ha sido un sencillo PC con una modesta CPU Intel Atom (aunque bien podría servir un miniportátil de alumno, una Raspberry Pi, una Android TVBox o incluso un móvil antiguo o tablet con salida HDMI). Una vez montada quedará así:


Mi idea es instalar un Xubuntu pelado con un usuario local que haga autologin y abra un navegador que reproduzca el contenido de una página web a pantalla completa, en modo "kiosco". La página web en mi caso está en el mismo PC desde donde se reproduce todo, pero igualmente podría estar en un servidor del centro (lo cual sería imperativo en el caso de usar una SmartTV o un dispositivo Android).

Para ello tenemos un script panel.sh que hace:
#!/bin/bash
firefox http://localhost/intranet/panel.html?tipo=slide & # Tipo video, flow, slide, panel
sleep 10
xdotool search --sync --onlyvisible --class "Firefox" windowactivate key F11
Este script se ejecuta al iniciar sesión desde un fichero /etc/xdg/autostart/panel.desktop usual. El comando xdotool envia a Firefox una pulsación simulada de la tecla F11, con lo cual se pone a pantalla completa, sin decoración de barras de ningún tipo.

Lo que usamos es un fichero .html lo mas versátil posible, con la siguiente estructura:

  • En la parte izquierda tenemos una zona donde se proyecta la información, luego lo veremos pero la idea es poder proyectar presentaciones de Google Slides, vídeos y otras fuentes de información.
  • En la parte superior derecha pondremos un widget meteorológico, con la predicción del tiempo en Cáceres. El usado es el generado en la página tiempo.es
  • En la parte inferior derecha pondremos un reloj-calendario hecho en Javascript a partir de este ejemplo con algunos retoques estéticos y de idioma .
  • Debajo del reloj nos sobra un pequeño espacio y ponemos allí el logotipo del IES.

Como la idea es que la parte de proyección pueda mostrar varios contenido incluimos un parámetro "tipo" que pasaremos en la URL y que puede tomar 4 valores (en el script panel.sh anterior se ve como se pasa dicho parámetro al hacer la petición, por ejemplo: http://localhost/intranet/panel.html?tipo=slide):

  • video: para proyectar vídeos en formato mp4. Normalmente actividades del IES. Permite reproducir un array de vídeos en bucle continuo. Los vídeos tienen un tamaño de 1280x720 y un bitrate de aproximadamente 1000kbps, dando una calidad suficiente y sin necesitar mucha potencia para reproducirlos. Si no lo tenemos en ese formato lo podremos convertir con la utilidad avconv (avconv -i input.mp4 -s 1280x720 output.mp4).
  • flow: para proyectar vídeos en el viejuno formato flv, usa el widget flowplayer, basado en Flash. Tienen menor calidad y gasta mas recursos de cpu que los mp4, pero ya tenía contenidos previos en dicho formato de otros años y no quería perder la posibilidad de proyectarlos.
  • slide: para proyectar presentaciones de Google Slides compartidas de forma pública por su creador (explicado en Embed a document, spreadsheet, or presentation).
  • panel: para mostrar el contenido de la URL http://panelinformativo.educarex.es, que es el sistema de gestión de los paneles oficiales de la Consejería. Los usuarios autorizados de cada centro pueden entrar a través de http://panelinformativo.educarex.es/login y dar de alta noticias y/o mensajes (con fechas configurables de activación y caducidad), que serán mostradas en la URL antes citada de forma automática en formato "carrusel" (filtrando lo mostrado en centro en función de la IP peticionaria, claro está). Este es el sistema que tienen los paneles enviados por la Consejería .

Cada vez que queremos cambiar los vídeos o la presentación debemos editar el fichero panel.html. Ya sé que eso es hard coding pero no me he puesto a automatizar la configuración mediante ficheros externos. Dentro del código HTML que pongo a continuación marco en rojo las partes que habría que cambiar para adaptar a las circunstancias de cada cual. También he cambiado la IP del pc donde están los contenidos web poniendo 172.X.Y.Z.

La página está calculada para una resolución de 1360x768, siendo todas las anchuras expresadas en pixeles para cuadrar perfectamente. La parte del vídeo/presentación tiene un ancho de 1000px aproximadamente y la de los widgets unos 330px. En caso de usar otras resoluciones habría que ir probando hasta que cuadrase.

Empezamos viendo la parte HEAD de panel.html. En ella definimos los CSS de los widget tiempo y reloj, así como todo el javascript necesario:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html style="height: 100%;">
<head>
  
  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
  <title>panel.html</title>

  <!-- ======================================================================================================================================================= -->
  <!--Importante: poner la IP fija definitiva. Si no se pone, no funciona. Nada de localhost.-->
  <!-- ======================================================================================================================================================= -->

  <!-- ======================================================================================================================================================= -->
  <!-- Antiguo Widget Flowplayer basado en flash para videos .flv --->
  <!-- ======================================================================================================================================================= -->
  <script src="http://172.X.Y.Z/intranet/javascript/flowplayer-3.2.8.min.js"></script>
  <script src="http://172.X.Y.Z/intranet/javascript/jquery.js" type="text/javascript" charset="utf-8"></script>
  <script src="http://172.X.Y.Z/intranet/javascript/flowplayer.playlist-3.2.8.min.js" type="text/javascript" charset="utf-8"></script>

  <style type="text/css">
        a.player {
            display: block;
            width: 560px;
            height: 420px;
            text-align: center;
            text-decoration: none;
            cursor: pointer;
            background: #333333;
        }

        a.player img {
            margin-top: 110px;
            border: 0;
            opacity: 0.8;
            filter: alpha(opacity=80);
        }

        a.player img:hover {
            opacity: 1;
            filter: alpha(opacity=100);
        }


        a.player {
            margin-top: 0px;
            width: 500px;
        }
  </style>  

  <!-- ======================================================================================================================================================= -->
  <!-- CSS Reloj -->
  <!-- ======================================================================================================================================================= -->

  <style type="text/css">
 body{
   font:bold 12px Arial, Helvetica, sans-serif;
   margin:0;
   padding:0;
   color:#bbbbbb; 
 }

 a { 
  text-decoration:none; 
  color:#00c6ff;
 }

 h1 {
  font: 4em normal Arial, Helvetica, sans-serif;
  padding: 20px; margin: 0;
  text-align:center;
 }

 h1 small{
  font: 0.2em normal  Arial, Helvetica, sans-serif;
  text-transform:uppercase; letter-spacing: 0.2em; line-height: 5em;
  display: block;
 }

 h2 {
     font-weight:700;
     color:#bbb;
     font-size:50px;
 }

 h2, p {
  margin-bottom:10px;
 }

 @font-face {
     font-family: 'BebasNeueRegular';
     src: url('fonts/BebasNeue-webfont.eot');
     src: url('fonts/BebasNeue-webfont.eot?#iefix') format('embedded-opentype'),
   url('fonts/BebasNeue-webfont.woff') format('woff'),
   url('fonts/BebasNeue-webfont.ttf') format('truetype'),
   url('fonts/BebasNeue-webfont.svg#BebasNeueRegular') format('svg');
     font-weight: normal;
     font-style: normal;

 }

 .container {width: 300px; margin: 0 auto; overflow: hidden;}

 .clock {width:280px; margin:0 auto; padding:5px; border:1px solid #333; color:#fff; }

 #Date { font-family: Arial, 'BebasNeueRegular', Helvetica, sans-serif; font-size:20px; text-align:center;  }

 ul { width:280px; margin:0 auto; padding:0px; list-style:none; text-align:center; }
 ul li { display:inline; font-size:50px; text-align:center; font-family: Arial, 'BebasNeueRegular', Helvetica, sans-serif; }

 #point { position:relative; -moz-animation:mymove 1s ease infinite; -webkit-animation:mymove 1s ease infinite; padding-left:10px; padding-right:10px; }

 @-webkit-keyframes mymove 
 {
 0% {opacity:1.0; text-shadow:0 0 20px #00c6ff;}
 50% {opacity:0; text-shadow:none; }
 100% {opacity:1.0; text-shadow:0 0 20px #00c6ff; } 
 }


 @-moz-keyframes mymove 
 {
 0% {opacity:1.0; text-shadow:0 0 20px #00c6ff;}
 50% {opacity:0; text-shadow:none; }
 100% {opacity:1.0; text-shadow:0 0 20px #00c6ff; } 
 }

  </style>
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
  <script type="text/javascript">
 $(document).ready(function() {
 // Create two variable with the names of the months and days in an array
 var monthNames = [ "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" ]; 
 var dayNames= ["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"];

 // Create a newDate() object
 var newDate = new Date();
 // Extract the current date from Date object
 newDate.setDate(newDate.getDate());
 // Output the day, date, month and year    
 $('#Date').html(dayNames[newDate.getDay()] + " " + newDate.getDate() + ' ' + monthNames[newDate.getMonth()] + ' ' + newDate.getFullYear());

 setInterval( function() {
  // Create a newDate() object and extract the seconds of the current time on the visitor's
  var seconds = new Date().getSeconds();
  // Add a leading zero to seconds value
  $("#sec").html(( seconds < 10 ? "0" : "" ) + seconds);
  },1000);
 
 setInterval( function() {
  // Create a newDate() object and extract the minutes of the current time on the visitor's
  var minutes = new Date().getMinutes();
  // Add a leading zero to the minutes value
  $("#min").html(( minutes < 10 ? "0" : "" ) + minutes);
     },1000);
 
 setInterval( function() {
  // Create a newDate() object and extract the hours of the current time on the visitor's
  var hours = new Date().getHours();
  // Add a leading zero to the hours value
  $("#hours").html(( hours < 10 ? "0" : "" ) + hours);
     }, 1000);
 
 }); 
  </script>

  <!-- ======================================================================================================================================================= -->
  <!-- Codigo Javascript para poner carrusel de Videos modernos: mp4 y webm. -->
  <!-- ======================================================================================================================================================= -->
  <script type="text/javascript">

      var videoSources = ["videos/terror.mp4"]; //Array con la lista de videos a reproducir
      var currentIndex = 0;

      //listener function changes src
      function myNewSrc() {
           var myVideo = document.getElementsByTagName('video')[0];
           myVideo.src = videoSources[currentIndex];
           myVideo.load();
      }
      // add a listener function to the ended event
      function myAddListener(){
           var myVideo = document.getElementsByTagName('video')[0];
           currentIndex = (currentIndex+1) % videoSources.length;
           myVideo.src = videoSources[currentIndex];
           myVideo.addEventListener('ended', myNewSrc, false);
      }
  </script>


  <!-- ======================================================================================================================================================= -->
  <!-- Codigo Javascript para ver que tipo de objeto se muestra (video normal, presentacion google o video flv en función del parametro get de la url tipo=XXX -->
  <!-- ======================================================================================================================================================= -->

  <script type="text/javascript">

      // analiza el queryString buscando el parametro tipo=XXX en la URL para decidir que mostrar.
      function muestraDiv() {

        var params = {};
        params.tipo="video"; // video, slide, flow, panel, ... Por defecto, video
        if (location.search) {
            var parts = location.search.substring(1).split('&');

            for (var i = 0; i < parts.length; i++) {
                var nv = parts[i].split('=');
                if (!nv[0]) continue;
                params[nv[0]] = nv[1] || true;
            }
        }
        divVideo=document.getElementById("div-video");
        divSlide=document.getElementById("div-slide");
        divFlow=document.getElementById("div-flow");
        divPanel=document.getElementById("div-panel");
        switch (params.tipo) {
                  case "video": 
                        purgaElemento(divSlide);
                        purgaElemento(divFlow);
                        purgaElemento(divPanel);
                        divVideo.style.display = 'block';     
                        myNewSrc(); //Carga el primer video
                        break;
                  case "slide":
                        divSlide.style.display = 'block';  
                        purgaElemento(divFlow);
                        purgaElemento(divVideo);
                        purgaElemento(divPanel);
                        break;
                  case "flow":
                        divFlow.style.display = 'block';  
                        purgaElemento(divSlide);
                        purgaElemento(divVideo);
                        purgaElemento(divPanel);
                        break;
                  case "panel":
                        divPanel.style.display = 'block';  
                        purgaElemento(divSlide);
                        purgaElemento(divVideo);
                        purgaElemento(divFlow);
                        break;
        }
      }
      
      function purgaElemento(oDiv) {
            oDiv.style.display = 'none';     
            oDiv.outerHTML = "";
            delete oDiv;
      }

  </script>
    
</head>
En la parte BODY definimos una tabla para formatear las partes la página (lo siento, aprendí HTML en los años 90 y sigo formateando con tablas en lugar de con DIV+CSS) y llamamos a la funcion javascript muestraDiv, que analiza el parámetro tipo y decide cual de los cuatro div (div-video, div-slide, div-flow o div-panel) mostrar, ocultando y destruyendo los otros tres.

Para el reloj y el widget tiempo pongo el código html que se generaba en las respectivas páginas de donde los he sacado:

<body style="height: 90%;" bgcolor="#000815"  onload="muestraDiv();">  <!--  muestraDiv-> decide que div aparece -->
   <table width="100%" style="height: 100%;" >
        <tr valign="middle">
           <td rowspan="2">

                <!-- ======================================================================================================================================================= -->
                <!-- UN DIV PARA CADA TIPO DE CONTENIDO MOSTRADO. SOLO UNO ES VISIBLE, LOS OTROS SE OCULTAN Y DESTRUYEN                                                      -->
                <!-- ======================================================================================================================================================= -->
               
                <!-- ======================================================================================================================================================= -->
                <!-- VIDEO MP4/WEBM                                                                                                                                          -->
                <!-- ======================================================================================================================================================= -->
                <div id="div-video" style="display: none">
                        <video width="990" poster="logo.png" height="740" autoplay preload="auto" controls id="miVideo" onended="myAddListener()" >
                          <source src="" type="video/mp4" />
                          Your browser does not support the video tag.
                        </video>
                </div>
                <!-- ======================================================================================================================================================= -->
                <!-- SLIDE                                                                                                                                                   -->
                <!-- ======================================================================================================================================================= -->
                <div id="div-slide" style="display: block"> <!-- si lo intentamos ocultar da error en la carga inicial -->
                        <div style="width:1000px;height:720px;overflow:hidden;" >  <!-- esto es para ocultar la barra de controles -->
                                  <iframe src="https://docs.google.com/presentation/d/e/2PACX-1vQ4WA8UkpxrJeeDdQOI-wIQ7S9kEpNoHZYpXxUvorCir-gTVoo2fWVyLN8VShiDivdbvtHZy4w1VRD1/embed?start=true&loop=true&delayms=5000" 
                                        frameborder="0" width="1000" height="750" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true">
                                </iframe>
                        </div>
                </div>
                <!-- ======================================================================================================================================================= -->
                <!-- PANEL                                                                                                                                                   -->
                <!-- ======================================================================================================================================================= -->
                <div id="div-panel" style="display: none">
                        <div style="width:1000px;height:750px;overflow:hidden;" >  <!-- esto es para ocultar la barra de controles -->
                                  <iframe src="http://panelinformativo.educarex.es" 
                                        frameborder="0" width="1000" height="750" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true">
                                </iframe>
                        </div>
                </div>
                <!-- ======================================================================================================================================================= -->
                <!-- VIDEO FLASH                                                                                                                                             -->
                <!-- ======================================================================================================================================================= -->
                <div id="div-flow" style="display: none">
                        <script>
                                $(function() {
                                    // set up player without "internal" playlists
                                    $f("player", "http://172.X.Y.Z/intranet/javascript/flowplayer-3.2.9.swf", {
                                        clip: { baseUrl: 'http://172.X.Y.Z/intranet/videos/antiguos' }
                                    // use playlist plugin. again loop is true
                                    }).playlist("#myplaylist", {loop:true});
                        });
                        </script>
                        <div style="float:left" id="myplaylist">
                             <a href="LipDub2016.flv"></a>
                             <a href="video-estetica.flv"></a>
                        </div>
                        <!-- player container without nested content -->
                        <a class="player" id="player"  style="width:1000px;height:750px;float:left"  href="videos/antiguos/bachillerato.flv"></a>
                </div>
                <!-- ======================================================================================================================================================= -->
                <!-- FIN DIVs                                                                                                                                                -->
                <!-- ======================================================================================================================================================= -->
           </td>
           <td width="330px" height="200px">        
             <!-- ======================================================================================================================================================= -->
             <!-- WIDGET TIEMPO                                                                                                                                           -->
             <!-- ======================================================================================================================================================= -->
             <div style="height:390px; width:330px; overflow-x:hidden; " valign="center">                               
                   <div id="tiempo_033974258b5fb50f89ccd5071c8f4f8c"> 
                   <div></div>
                   <div> 
                       <img src="//www.tiempo.es/build/img/logo/tiempo133.png" width="80" height="18" alt="tiempo.es"> </div> 
                       <script type="text/javascript" src="//www.tiempo.es/widload/es/ver/320/340/010/es0ca0037/033974258b5fb50f89ccd5071c8f4f8c.js">
                       </script> 
                   </div>
             </div>
           </td>
        </tr>
        <tr valign="top" align="middle">
           <td>
              <!-- ======================================================================================================================================================= -->
              <!-- WIDGET RELOJ                                                                                                                                           -->
              <!-- ======================================================================================================================================================= -->
              <div class="container">
                <div class="clock">
                <div id="Date"></div>
                        <ul>
                            <li id="hours"> </li>
                            <li id="point">:</li>
                            <li id="min"> </li>
                            <li id="point">:</li>
                            <li id="sec"> </li>
                        </ul>
                </div>
              </div>
              <br/>
              <img src="logo.png" width="290px" height="210px"/>
           </td>
        </tr>
   </table>

</body></html>
El código html completo de panel.html y varias cosas más podemos descargarlo de aquí. No olvidemos adaptar las IP y los nombres de los vídeos, ficheros de imagen, enlaces a presentaciones y el widget del tiempo a nuestro caso particular.

El resultado final que aparece en la pantalla es:



Bueno, pues con todo esto ya tenemos nuestra pantalla hecha con materiales de derribo.

viernes, 1 de diciembre de 2017

Desactivando la red wifi de 5Ghz en DLink DIR-860L con dd-wrt

Nuestros puntos de acceso DLink traen de serie 2 wifis: la de 2.4Ghz y la de 5Ghz, cada una con SSID y gestión de contraseñas independiente. La mayoría de los portátiles de los que disponemos en los centros solo ven la red de 2.4Ghz y los programas de gestión (SiaticControl y ControlWifi) que tenemos solo manejan esa frecuencia.

Como consecuencia de ello la red de 5Ghz estará ociosa a no ser que la configuremos a mano via ssh o interfaz web. Como algunos de nosotros queremos dejarla apagada en tanto en cuanto no la necesitemos estuve investigando como hacerlo entrando por ssh al DLink.

Lo que hacemos cambiar el SSID y contraseña que trae por defecto dicha red e indicar que el comando "ifconfig ba0 down" se ejecute en el arranque del sistema dd-wrt. El interface "ba0" es el correspondiente a la red de 5Ghz (en el caso de la de 2.4Ghz es "ra0"):
nvram set wl1_ssid=NUEVO_SSID_5GHZ
nvram set wl1_wpa_psk=NUEVA_PASSWORD_5GHZ
nvram set rc_startup="ifconfig ba0 down"
nvram commit
reboot
Y con esto nos despedimos hasta una nueva pildorita sobre nuestros DLink.

martes, 28 de noviembre de 2017

Reinicio periódico en DLink DIR-860L con dd-wrt

Es muy conveniente reiniciar algunos sistemas de forma periódica. En el caso de nuestros puntos de acceso DLink DIR-860L es casi obligatorio ya que cuanto mas tiempo pasan en funcionamiento mas empiezan a fallar ciertos aspectos, lo cual nos obliga al final a hacer un reset manual.

Mis compañeros Paco y Noemí me descubrieron que el propio dd-wrt incorpora un mecanismo de reinicio periódico, sin necesidad de configurar a mano crontab. Aunque se puede definir usando el interface web, buscando la opción "Administracion->Keep alive" entre sus pestañas nosotros lo vamos a hacer por comando. Consiste en entrar por ssh y ejecutar en cada punto de acceso los siguientes comandos:
nvram set schedule_enable=1
nvram set schedule_hour_time=2
nvram set schedule_weekdays=*
nvram set schedule_hours=7
nvram set schedule_minutes=0
nvram set schedule_time=3600
nvram set ntp_enable=1
nvram set ntp_server=..ip servidor centro..
nvram set ntp_mode=auto
nvram commit
reboot
Comentemos:
  • schedule_enable: puesto a 1 habilita el reinicio programado.
  • schedule_hour_time: puesto a 1 activa el reinicio cada X segundos, puesto a 2 el reinicio a dias/horas determinados.
  • schedule_weekdays, schedule_hours, schedule_minutes: días y hora donde se realiza el reinicio. En mi caso, todos los dias a las 7:00 de la mañana.
  • schedule_time: segundos entre reinicio si schedule_hour_time=1.
  • ntp_enable: puesto a 1 habilita la sincronización de hora por ntp.
  • ntp_mode: con valor auto se realiza una sincronización automática.
  • ntp_server: IP del servidor ntp de nuestra red. Normalmente es el servidor principal del centro.
El servidor ntp se configura para que el punto de acceso se ponga en hora y haga los reinicios en el momento correcto, ya que si no se piensa que estamos en 1970 y a una hora intempestiva del día. Una vez arrancado el punto de acceso tarda un rato en sincronizar la hora, parece ser que no le corre prisa eso, pero si somos pacientes al final coge la hora y fecha correcta.

Gracias a estos reinicios nuestros puntos de acceso serán mucho mas estables y se portarán mejor. Nada como un buen reinicio para limpiar un sistema.

martes, 14 de noviembre de 2017

Conectar por ssh/vnc al PC del profesor tras el punto de acceso DLink DIR-860L

En esta entrada anterior vimos como hacer NAT en el aula usando un punto de acceso DLink DIR-860L, de manera que los PC de alumnos y profesor quedaban en red privada dentro del rango 192.168.0.X.

El punto de acceso tenía dos direcciones: la 172.X.Y.Z (que sería la pública del centro) y la 192.168.0.254 (dentro de la red privada del aula), haciendo NAT entre ellas. Esto aísla por completo el aula de la red del centro pudiendo asegurar que el DLink realiza el NAT de una forma bastante más eficiente que los equipos HP que nos trajeron a los infolab.

El problema derivado es que no podemos conectar por ssh ni por vnc con el ordenador del profesor, que queda detrás del DLink. Esto causa bastante trastorno ya que es muy normal conectar para realizar diversas tareas de forma remota. Existe una vía indirecta para conectar por ssh, entrando primero en el DLink y luego desde alli haciendo ssh root@192.168.0.100, pero aparte de ser lento no podemos usar ssh -X para abrir aplicaciones gráficas sobre la conexión.

La solución está en configurar el port forwarding en el DLink para redigir determinadas conexiones a 172.X.Y.Z para que acaben en el 192.168.0.100 de la red privada. Es el famoso "abrir puertos" que hemos hecho todos en el router de casa para usar torrent o juegos.

El método paso a paso para abrir puertos en el entorno web de DD-WRT de nuestro punto de acceso DLink está descrito en este vídeo.

Para redirigir ssh he optado conectar el puerto 23 de la IP externa con el puerto 192.168.0.100:22, mediante la opción NAT-Qos -> Port Forwarding:


Uso el puerto 23 porque el puerto 22 está reservado para el servicio sshd del punto de acceso y no quiero perder la posibilidad de hacer ssh al DLink. Una vez aplicados los cambios, si desde cualquier punto de la red del centro hacemos:
$ ssh -X -p 23 root@172.X.Y.Z
conectaremos con el PC del profesor, saltando de forma transparente el DLink. Al usar "-X" tenemos la posibilidad de abrir aplicaciones gráficas.

Para redirigir vnc he usado la opción NAT-Qos -> Port Range Forwarding:


Pongo el rango de puertos 5900-5910 ya que vnc va usándolos de forma secuencial si hay varias sesiones vnc abiertas. En este caso el mapeo de los puertos es directo: el 5900 de fuera se conecta con el 5900 de dentro. Teniendo arrancado el servidor vnc en el PC del profesor (en mi caso lo arranco a mano cuando lo necesito, entrando antes por ssh) desde fuera haremos:
$ vinagre 172.X.Y.Z 
y entramos en el pc interno al aula.

Con esto tendremos acceso a esos PC igual al que tenemos a cualquier otro PC de profesor del centro.

martes, 7 de noviembre de 2017

Personalizando la pantalla de login de los portátiles de alumnos.

Como ya dijimos en la anterior entrada lo mas extendido es asignar cada portátil a un mismo alumno durante todo el curso.

Para poner las cosas sencillas ponemos una etiqueta adhesiva en la tapa o en el inferior con los datos del portátil y del alumno, pero esta etiqueta se puede quitar/caer y, por otro lado, no es infrecuente que algunos alumnos estén en la luna de Valencia y acaben confundiendo sus portátiles.

Una buena solución es que en la pantalla de login salga algo que identifique al alumno justo antes de poner sus credenciales: una foto, un texto con su nombre, etc. De esta manera se minimiza la posibilidad de coger otro portátil de forma accidental ya que se darán cuenta al meter las credenciales. Mi compañero Manu hizo la aplicación autolabel, que toma los datos del árbol ldap (en su árbol ldap hay campos que asocian portátil y alumno) y modifica el greeter del gdm3 (fichero /etc/gdm3/greeter.gsettings) para mostrar en él la foto y nombre del alumno.

Con xubuntu nos hemos cambiado a lightdm que desgraciadamente no tiene la versatilidad de gdm3 para poner imágenes y textos en la pantalla de login. Pero que no cunda el pánico: siempre podemos coger la imagen de fondo del greeter y manipularla con la utilidad convert para añadir la foto y nombre del alumno.

Para ello tengo un script que obtiene el login del alumno asociado al portátil, coge su foto y otros campos (tal como ya hicimos en la entrada anterior) y modifica nuestro fondo estándar de lightdm, almacenado en "/usr/share/xfce4/backdrops/fondo_1a.jpg".
# cat cambia_background_greeter.sh 

#!/bin/bash
#Curso 17-18-v1.
nombre_pc=$(/bin/hostname)
ldaphost=ldap
base=dc=instituto,dc=extremadura,dc=es
userbase=ou=People,$base
interno="cn=interno,dc=instituto,dc=extremadura,dc=es"
pwd_interno="mipwdinterno"
tmp="/tmp"
fondo_original="/usr/share/xfce4/backdrops/fondo_1a.jpg"
fondo_bak="/usr/share/xfce4/backdrops/fondo_1a_bak.jpg"
fichero="/root/scripts/asignaciones.txt"

test -e $fondo_bak || cp -f $fondo_original $fondo_bak

linea=$(/bin/grep -i -e "^${nombre_pc};" $fichero)
if [ -z $linea ] # no encontrado nombre.
then
  echo "ERROR: No encuentro portatil. Abortado"
  exit 1
fi

#Llegados aquí, en $linea tenemos nombre-pc;mac;usuario;password;grupo
usuario=$(echo $linea | /usr/bin/cut -d";" -f3)
pass=$(echo $linea | /usr/bin/cut -d";" -f4)
grupo=$(echo $linea | /usr/bin/cut -d";" -f5)
nombre=$(ldapsearch -w $pwd_interno  -D $interno -xLLL -t -h $ldaphost -b $userbase "(&(objectClass=posixAccount)(uid=$usuario))" | grep "^cn: ")
nombre=${nombre:4} # quitamos "cn: "

rm -f $tmp/ldapsearch-jpegPhoto-*
ldapsearch -w $pwd_interno  -D $interno -xLLL -T $tmp -t -h $ldaphost -b $userbase "(&(objectClass=posixAccount)(uid=$usuario))" jpegPhoto
if [ -e $tmp/ldapsearch-jpegPhoto-* ]; then
  mv -f $tmp/ldapsearch-jpegPhoto-* $tmp/.face 2> /dev/null
else
  cp -f /root/scripts/anonymous.jpg $tmp/.face 2> /dev/null # Foto genérica para alumnos sin foto.
fi
convert $fondo_bak  \( $tmp/.face -resize 200x200 \) -geometry +1700+50 -composite -matte $tmp/output.jpg
comando="text 20,300 \"Alumno: $usuario\"; text 20,340 \"${nombre}\"; text 20,380 \"Grupo: ${grupo}\""
convert $tmp/output.jpg  -pointsize 30 -fill yellow -gravity northeast -draw "$comando" $fondo_original
rm $tmp/.face
rm $tmp/output.jpg

exit 0
Recordemos que en pwd_interno debemos poner la contraseña que hayamos puesto a nuestro usuario "interno" de ldap. Todo esto se distribuye y ejecuta una vez más desde una tarea puppet:

class xubuntu_portatil {
  .....
  .....
  .....
  #Etiquetado fondo lightdm con datos alumno

  file {"/root/scripts/cambia_background_greeter.sh":
        owner=>root, group=>root, mode=>755,
        source=>"puppet:///modules/xubuntu_portatil/cambia_background_greeter.sh",
        notify => Exec["etiqueta-lightdm"],
  }

  file {"/root/scripts/anonymous.jpg":
        owner=>root, group=>root, mode=>755,
        source=>"puppet:///modules/xubuntu_portatil/anonymous.jpg",
        notify => Exec["etiqueta-lightdm"],
  }

  exec { "etiqueta-lightdm":
                        path => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                        command => "/root/scripts/cambia_background_greeter.sh",
                        require => [File["/root/scripts/cambia_background_greeter.sh"],File["/root/scripts/anonymous.jpg"]],
                        refreshonly => true,
  }

  .....
  .....
  .....
}
El fichero anonymous.jpg es cualquier fichero con una foto genérica que queramos poner como imagen por defecto. Yo tengo una imagen tipo:


Tras ejecutarse el script al actualizar por puppet la pantalla de login del portátil quedaría tal que así:


El próximo año bastaría con cambiar el fichero asignaciones.txt distribuido en la tarea anterior y modificar el script un poco para que se baje y ejecute de nuevo.

Y con esto creo que acabo por ahora la serie de preparación de portátiles de alumnos.

Por último, no puedo dejar esto sin conmemorar que hoy es el centenario de la Revolución Rusa y nada mejor que una buena película sobre la gloriosa gesta del primer paseo espacial de Alexey Leonov para homenajearlo:


domingo, 5 de noviembre de 2017

Automatizando la configuración de portátiles de alumnos al inicio de cada curso.


Lo más común en nuestros centros es que los portátiles estén asignados durante todo el curso a un mismo alumno, que es responsable de su buen uso. Eso hace que cada año al comenzar el curso haya que realizar un proceso manual que comprende al menos los siguientes pasos:
  1. Cargar la imagen del sistema operativo en el portátil si fuere necesario, usando Clonezilla o DRBL.
  2. Limpiar datos del usuario anterior y actualizar el portátil.
  3. Copiar la configuración de la red wifi del aula donde va el portátil en el caso de que el portátil sólo vaya a usarse en una única estancia y con contraseña fija.
  4. Actualizar el nombre del portátil. Aquí tenemos 2 opciones: que el nombre coincida con el login del alumno o que el nombre del portátil sea independiente del mismo y permanezca invariable de un año a otro. Cada centro decide como gestionar la nomenclatura según su criterio.
  5. Cachear las credenciales del alumno que va a usar el portátil, para permitir que inicie sesión sin tener conexión a la red del centro.
El paso 1 ya hemos visto hasta la saciedad que puede hacerse de forma bastante automática y concurrente usando DRBL o Clonezilla.

El paso 2 ya contamos como realizarlo en la anterior entrada.

El paso 3 es sencillo: basta con copiar un fichero con la configuración a /etc/NetworkManager/system-connections/ de cada portátil. Ese fichero se saca conectando a la wifi con un portátil de forma manual y luego recuperando ese fichero de configuración para distribuirlo a los demás mediante puppet.

Veremos a continuación como automatizar lo más posible los pasos 4 y 5. Para ello necesitamos una manera de relacionar cada alumno con el portátil que tiene asignado y determinar el nombre del mismo, así como obtener mas información que pueda ser útil, como la contraseña que tendrá el usuario o el grupo al que pertenece.

Lo ideal sería tenerlo todo en una base de datos centralizada (ya sea una BBDD tradicional o en nuestro ldap) y consultar via un servicio web o similar, pero de momento no dispongo de dicho artefacto. Así que me he ido a lo más cutre: tengo un fichero de texto en formato .cvs con todos los datos que necesito. Dicho fichero lo copio via puppet y lo proceso en cada portátil. El formato del fichero es:
# cat asignaciones.txt 
porthp-o11;22:30:43:11:34:3A;aiglesiasz21;12433133;ESO3oA
porthp-o02;22:30:43:11:37:3C;rbarradop11;32212244;ESO3oB
......
Es un fichero con 5 columnas separadas por punto y coma: nombre del portátil, MAC de alguna tarjeta de red del portátil (normalmente ethX), nombre del alumno que lo tiene asignado, contraseña por defecto del alumno y grupo al que pertenece el mismo.

Al ser un .cvs es sencillo abrirlo en una aplicación de hoja de cálculo y rellenarlo de forma adecuada. Los datos de las MAC los podemos obtener de los facter almacenados en servidor:/var/lib/puppet/yaml/facts. Los datos de cada usuario se obtienen del fichero Alumnos.xml que descargamos desde Rayuela.

Este es el script que uso:
# cat cachea-credenciales.sh 
#!/bin/bash

fichero="/root/scripts/asignaciones.txt"  # fichero con las asignaciones de portatiles, con lineas csv en formato nombre-pc;mac;usuario;password;grupo. La mac puede ser "-"
                            # si el pc tiene nombre y se encuentra en el fichero, la mac es despreciada. En ese caso, el pc mantiene su nombre.
temporal="/tmp/macs_pc"
nombre_pc=$(/bin/hostname)
ldaphost=ldap
base=dc=instituto,dc=extremadura,dc=es
userbase=ou=People,$base
interno="cn=interno,dc=instituto,dc=extremadura,dc=es"
pwd_interno="mi_contrasena"

#Buscamos pc por nombre en fichero de asignaciones. 
#Nota: si queremos cambiar el nombre del pc en funcion de la mac habria que buscar aquí por mac primero y luego ajustar los nombres.
#Eso no lo hacemos, pero queda dicho.

linea=$(/bin/grep -i -e "^${nombre_pc};" $fichero)
if [ -z $linea ] # no encontrado nombre, buscamos por macs
then
  /sbin/ifconfig -a | /bin/grep HWa | tr -s ' ' | cut -f5 -d' ' | tr '[a-z]' '[A-Z]' | sort -u > $temporal # se cogen todas macs.
  macs_pc=$(/bin/cat $temporal)
  linea=$(/bin/grep -i -f $temporal $fichero)
  if [ -z $linea ]
  then 
     echo "ERROR: No encuentro las macs $macs_pc. Abortado"
     exit 1
  else
     #mac encontrada, hay que dar nombre al pc
     nombre_pc=$(echo $linea | /usr/bin/cut -d";" -f1)

     echo "127.0.0.1       localhost
127.0.1.1       $nombre_pc

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters" > /etc/hosts
     echo "$nombre_pc" >  /etc/hostname
     hostname -F /etc/hostname
  fi
fi

#Llegados aquí, en $linea tenemos nombre-pc;mac;usuario;password;grupo

usuario=$(echo $linea | /usr/bin/cut -d";" -f3)
pass=$(echo $linea | /usr/bin/cut -d";" -f4)
grupo=$(echo $linea | /usr/bin/cut -d";" -f5)

#Chequeamos credenciales por defecto del alumno  intentando acceder al campo privado employeeNumber
eNumber=$(ldapsearch  -w $pass -D "uid=$usuario,$userbase" -xLLL -t -h $ldaphost -b $userbase "(&(objectClass=posixAccount)(uid=$usuario))" employeeNumber | grep "^employeeNumber" | cut -d" " -f2)
if [ "$eNumber" != "" ]
then
   #Cachea credencial por defecto alumno
   /usr/sbin/cc_test -store any $usuario $pass 
   /usr/sbin/nss_updatedb ldap
else
   echo "WARNING: El alumno ha cambiado la contraseña, no se cachea."
fi

#Buscamos el homeDirectory del alumno en ldap
homeDirectory=$(ldapsearch -x -h $ldaphost -b $userbase "uid=$usuario" | grep "^homeDirectory" | cut -d" " -f2)
if [ "$homeDirectory" != "" ]
then
   #Si no existe se crea
   test -d $homeDirectory || /sbin/mkhomedir_helper $usuario
   #Y se baja la foto para guardarla en .face
   rm -f $homeDirectory/ldapsearch-jpegPhoto-*
   ldapsearch -w $pwd_interno  -D $interno -xLLL -T $homeDirectory -t -h $ldaphost -b $userbase "(&(objectClass=posixAccount)(uid=$usuario))" jpegPhoto
   if [ -e $homeDirectory/ldapsearch-jpegPhoto-* ]; then
          mv -f $homeDirectory/ldapsearch-jpegPhoto-* $homeDirectory/.face 2> /dev/null
          chown root  $homeDirectory/.face 2> /dev/null
          chmod 444 $homeDirectory/.face 2> /dev/null
   fi
fi

exit 0
Básicamente hace lo siguiente:
  • Obtenemos el nombre del portatil y lo buscamos en el fichero.
  • Si no lo encontramos, obtenemos su MAC y la buscamos. Si aparece la MAC, cogemos el nombre asociado y se lo damos al portátil.
  • Si no encontramos coincidencia en nombre ni MAC se acaba el script.
  • Cogemos el nombre de alumno, su clave y el grupo al que pertenece.
  • Verificamos contra ldap si el alumno mantiene esa clave y no la ha cambiado. Si es así la cacheamos.
  • Conseguimos en ldap su homeDirectory y lo creamos de forma local en el portátil usando mkhomedir_helper.
  • Por último, obtenemos de ldap su foto y la copiamos en el fichero .face de su home local. Es conveniente tenerla para algunas aplicaciones.
  • Para obtener la foto usamos el usuario "interno" que tiene definido nuestro arbol ldap. Este usuario tiene una clave en principio desconocida, pero la podemos cambiar nosotros mismos usando phpldapadmin y luego la ponemos a mano en el script, en la variable "pwd_interno".
A modo de sugerencia, otras cosas que no hace el script pero que sería interesante realizar:
  • Comprobar primero la MAC y ver si el nombre en el portátil coincide con el nombre en el fichero, renombrando en ese caso el portátil al del fichero.
  • Usar el grupo para otras tareas útiles, por ejemplo determinar y autoconfigurar la red wifi a la que se conectará.
Por último, distribuimos ambos ficheros desde nuestra tarea puppet favorita:
class xubuntu_portatil {
  ....
  ....
  file {"/root/scripts/asignaciones.txt":
        owner=>root, group=>root, mode=>500,
        source=>"puppet:///modules/xubuntu_portatil/asignaciones.txt",
  }


  file {"/root/scripts/cachea-credenciales.sh":
        owner=>root, group=>root, mode=>755,
        source=>"puppet:///modules/xubuntu_portatil/cachea-credenciales.sh",
  }
  ....
  ....
}
Podríamos automatizar también su ejecución, como lo tenemos hecho en el script de limpieza de credenciales, pero de momento prefiero lanzarla a mano en cada portátil mientras no adquiere mas rodaje. Ya habrá tiempo de hacerlo de forma desatendida en un futuro.

Todo esto es una mezcolanza de ideas tomadas en los últimos años de scripts que han hecho varios compañeros y han compartido conmigo. ¡Gracias Nando, Manu, Antonio Abasolo y seguramente alguno más!

martes, 31 de octubre de 2017

Limpieza de portátiles de alumnos a principio de curso.

A principio de curso siempre tenemos que preparar los portátiles asignados a los alumnos, empezando por hacer una limpieza general de los mismos. Con los años hemos ido automatizando esto lo más posible para evitar tener que repetir una serie de tareas portátil a portátil.

Aparte de ejecutar puppet y pkgsync para actualizar convenientemente los portátiles, es recomendable borrar las credenciales cacheadas de antiguos usuarios, sus homes y las conexiones wifi creadas en el pasado por los alumnos. Este sería el script a ejecutar al inicio de curso:
# cat limpia_credenciales.sh

#!/bin/bash
#Curso 17-18. Version v1.

#Limpia credenciales cacheadas
for i in $(cc_dump | awk ' {print $3} '); do  cc_test -update any $i -  ; done

#Limpia conexiones wifi antiguas
rm /etc/NetworkManager/system-connections/*

#Limpia homes antiguos
rm -rf /home/alumnos/*

exit 0
Para ejecutarlo la forma mas sencilla es usar unas reglas puppet que aplicaríamos a los portátiles. En mi caso tengo un módulo puppet "omnibús" llamado xubuntu_portatil donde tengo varias pequeñas tareas de este tipo.

El esquema es el típico de estos casos: si no existe o ha cambiado, copiamos el script al portátil y lo ejecutamos.
class xubuntu_portatil {
  
  .......
  .......
  #Limpieza de credenciales a inicio de curso. El script se ejecuta una vez. Si queremos que se ejecute de nuevo
  #hay que cambiar el contenido (p.ej. la versión)

  file {"/root/scripts":
     ensure => directory,
     before => File["/root/scripts/limpia-credenciales.sh"],
  }

  file {"/root/scripts/limpia-credenciales.sh":
        owner=>root, group=>root, mode=>755,
        source=>"puppet:///modules/xubuntu_portatil/limpia-credenciales.sh",
        notify => Exec["limpia-credenciales"],
  }

  exec { "limpia-credenciales":
                        path => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                        command => "/root/scripts/limpia-credenciales.sh",
                        require => File["/root/scripts/limpia-credenciales.sh"],
                        refreshonly => true,
  }
  .......
  .......

}
Cada vez que queramos hacer limpieza basta con cambiar algo (basta, por ejemplo, con cambiar la fecha o nº de versión de v1 a v2) en el script almacenado en el directorio "files" de nuestro módulo xubuntu_portatil y se copiará de nuevo y ejecutará.