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

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: