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

miércoles, 24 de abril de 2024

Chequear mediante javascript si está instalada una extensión de Google Chrome

Que complicadas son a veces las cosas. Necesitaba asegurarme en la parte javascript de una página web de que Google Chrome tenía instalada una extensión determinada y me encuentro que no hay ninguna función en el API de Chrome que permita averiguarlo. La única manera es mediante métodos indirectos y cutres: intentar hacer algo con la extensión y capturar el error que salta si no está presente.

Hay varías maneras de hacerlo, esta es simplemente una de ellas. Necesitamos saber:
  1. La ID de la extensión, que es como su DNI que la identifica de forma universal. La instalamos desde https://chromewebstore.google.com/ y luego vamos a Gestionar Extensiones de nuestro Chrome.
  2. Elegimos la extensión en cuestión en "Gestionar Extensiones" y le damos a "Detalles". En la barra de la URL aparece el ID, por ejemplo: chrome://extensions/?id=pmlcjncilaaaemknfefmegedhcgelmee.
  3. Visualizamos al manifiesto de la extensión para ver la parte pública de la misma, esto se hace accediendo con la URL: chrome-extension://pmlcjncilaaaemknfefmegedhcgelmee/manifest.json (cambiar la parte en negrita por el ID de tu extensión).
  4. En el manifiesto, que es una estructura .json localizamos el atributo "web_accessible_resources.resources". Alli se enumeran los recursos de la extensión que son públicos.
  5. Elegimos uno de esos recursos públicos. Por ejemplo, dado el manifiesto:
     {
       "action": {
          "chrome_url_overrides": {
             "newtab": "popup.html"
          },
          "default_title": "__MSG_extName__"
       },
       "background": {
          "service_worker": "js/background.js"
       },
       "content_scripts": [ {
          "all_frames": false,
          "js": [ "js/content.js" ],
          "match_about_blank": false,
          "matches": [ "\u003Call_urls>" ],
          "run_at": "document_start"
       } ],
       "default_locale": "en",
       "description": "__MSG_extShortDesc__",
       "host_permissions": [ "\u003Call_urls>" ],
       "icons": {
          "128": "icons/icon-128.png",
          "16": "icons/icon-16.png",
          "300": "icons/icon-300.png",
          "48": "icons/icon-48.png"
       },
       "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAT6Z9x4SMUH91sYicM+8qwroIqnrZoMYW4zd+0IX3DGnEtzUX6bNusmBjiwKydV9LvASRM5o12bmc+XuWsYr/G/YwzoKuVfmLHgr8JVfJDwS67XlYKHc1YeSLDI1r7mVrP2nr6W6MBfxRuxWz4QoAZxnaQkw9Dw+SNQksGQq2BIDt12uqSFavzEILGcURMM1+YdILVAEHLSiGVwiLveZfcZzPhS91tJNgawW/nagxxe7iuRE0ea0h+m4I9b3cHCW1oc22nUR9FIliXsslM1F6BZNQDxJoHBJM01+Jq5VDpInXTykeHvL2+v9JRhRrizeLsdRcPBKyIFZDROXQnR+wIDAQAB",
       "manifest_version": 3,
       "minimum_chrome_version": "88.0",
       "name": "__MSG_extName__",
       "offline_enabled": true,
       "permissions": [ "storage", "scripting", "alarms" ],
       "update_url": "https://clients2.google.com/service/update2/crx",
       "version": "1.0.5",
       "web_accessible_resources": [ {
          "matches": [ "\u003Call_urls>" ],
          "resources": [ "icons/*" ],
          "use_dynamic_url": false
       } ]
    }
    
    Podríamos escoger el recurso icons/icon-16.png. La URL para acceder a él sería: chrome-extension://pmlcjncilaaaemknfefmegedhcgelmee/icons/icon-16.png. Si abrimos esa URL en el navegador debería cargarse dicho icono. Ojo: igual que hemos elegido un .png podríamos haber elegido un .html o un .txt, no importa el tipo de recurso.
  6. La URL elegida anteriormente será la que usemos para chequear si existe la extensión desde JavaScript. Intentaremos acceder a dicha URL y si obtenemos HTTP 200 OK es que la extensión está instalada y activa, en caso contrario la extensión no está disponible. Es feo, pero funciona.


La función quedaría:
function extensionInstalada() {

	 var url = 'chrome-extension://pmlcjncilaaaemknfefmegedhcgelmee/icons/icon-16.png';
	 var hasExtension=null;

	 var xhr = new XMLHttpRequest();
	 xhr.onreadystatechange = function() {
		 if (xhr.readyState != 4) return;
		 if (xhr.status === 200) {
			   hasExtension=true;
		 }
	 };
	 xhr.open("GET", url, false);
	 try {
		  xhr.send();
	 } catch (error) {
		 hasExtension=false;
	 }
	 return hasExtension;

}  
El código JavasCript que comprobaría la extensión sería:
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == false) {
	 alert("Esto solo funciona con Google Chrome");
	 return;
}
resultado=extensionInstalada();
if (!resultado) {
	 alert("No tienes instalada la extensión XXX.");
	 return;
}
Y ya está. No me digan que Google no podría currarselo un poquito para hacerlo más sencillo.

Puppet: regla para instalar el paquete linux-headers del kernel actual.

Tras migrar a Xubuntu 22, quería instalar mediante puppet en los infolab el driver "nvidia-driver-390" y "nvidia-dkms-390". Estos paquetes necesitan que se haya instalado previamente el paquete linux-headers-xxx correspondiente al kernel que corre actualmente el sistema operativo.

El problema es que ninguno ambos paquetes instala linux-headers y, para mas inri, si no lo encuentran lo que sucede es que falla lo instalación. Tenemos que instalar nosotros a mano previamente el linux-headers-xxx y luego los nvidia-tal-390. Como odio hacer a mano cosas que son automatizables he encontrado la manera de instalarlo todo de un tirón usando puppet. Las reglas serían:
...
   # Facter $kernelrelease => 5.15.0-105-generic
  $kernel=regsubst($kernelrelease,'-generic', '', 'G') # Quitamos -generic y queda "5.15.0-105"
  
  package {"linux-headers-$kernel" : ensure =>installed }
  package {"nvidia-driver-390" : ensure =>installed }
  package {"nvidia-dkms-390" : ensure => installed }
  ....
Explicación: el problema es saber desde puppet que versión de los linux-headers tenemos que instalar. El truco está en usar el facter $kernelrelease, que nos devuelve el kernel instalado en la forma "xxx-generic". Si quitamos la parte "-generic" (con la función de puppet "regsubst") nos quedará la versión del kernel, que unida a "linux-headers-" nos permitirá instalar el paquete correcto. Voilà!

Puppet: regla para instalar VirtualBox completo en Ubuntu 22

Si queremos instalar VirtualBox al completo (VirtualBox, Guest Additions y Extension Pack) de forma automatizada mediante puppet en los equipos de nuestra red las reglas serían:
   ...
   package {"virtualbox": ensure => installed }
   ->
   package {"virtualbox-guest-additions-iso": ensure => installed }
   ->
   exec { "accept-virtualbox-license":
            command => "/bin/sh -c \"echo virtualbox-ext-pack virtualbox-ext-pack/license select true | sudo debconf-set-selections\""
   }
   ->
   package {"virtualbox-ext-pack" : ensure => installed }
   ....
La parte mas exótica es el "accept-virtualbox-license", que permite instalar luego el paquete virtualbox-ext-pack de forma automática sin preguntas al usuario. Si no se pone la instalación es interactiva (pregunta si aceptamos la licencia) y la regla puppet falla. De esta manera preparamos la respuesta a la pregunta antes de instalar el paquete.

Es de agradecer que ahora la instalación es mucho más sencilla que antaño, gracias a que en Ubuntu 22 se ha metido el paquete "virtualbox-ext-pack", que busca y descarga las Extension Pack adecuadas. Antes había que hacerlo todo a mano.

viernes, 23 de febrero de 2024

Cada alumno con su portátil

En mi centro muchos portátiles tienen uno o dos alumnos asignados, de tal manera que solo deberían ser usados por ellos. Esto tiene como consecuencia que no suelan sufrir desperfectos ya que al ser nominativos se incentiva su cuidado.

Pero este curso, no sé por que motivo, nos hemos encontrado en que los alumnos estaban usando los portátiles de forma libre, cogiendo el de sus compañeros al azar. Me han pedido que controlemos eso, evitando que nadie pueda usar un portátil que no sea el que tiene asignado. Está es la solución que he implementado.

Por un lado un script que se ejecuta al inicio de la sesión de usuario con:
# cat /etc/xdg/autostart/CheckUser.desktop                                                                
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Icon[es_ES]=
Exec=/usr/local/bin/check_user
Name[es_ES]=Check authorized user
Name=Check authorized user
El script sería:
# cat /usr/local/bin/check_user
#!/bin/bash

usuario=$USER
equipo=$HOSTNAME
fecha=$(date)
file=/var/cache/.asignaciones.txt

#Si no hay fichero de asignaciones, salimos
test -e "$file" || exit 0

#Buscamos usuarios permitidos
permitidos=$(grep "^${equipo};" $file| cut -d";" -f3)

#Si no hay usuarios asignados, permitimos que lo use cualquiera
test -z "$permitidos"  &&  exit 0
test "$permitidos" == "-"  && exit 0

#Buscamos si usuario actual está permitido    
autorizado=$(echo $permitidos | grep -w $usuario)
test -z "$autorizado" || exit 0

sleep 5

#Cerramos los navegadores para que el usuario vea el mensaje
killall -9 firefox
killall -9 google-chrome
#Si llegamos aqui estamos intentando hacer login un un usuario no autorizado en el portátil
zenity --error --text="Usuario no autorizado en este portátil. Avise al profesor." --timeout 10 --width=600 --height=100
xfce4-session-logout

exit 0
Lo que hace es obtener el nombre de usuario y máquina y busca en el fichero /var/cache/.asignaciones.txt si el usuario está permitido en dicha máquina. Si no lo está, le manda un aviso y cierra la sesión.

El fichero /var/cache/.asignaciones.txt lo distribuyo por puppet en todos los portátiles y tiene este formato:
...
porthp-o19;b0:5a:da:a2:2d:0c;agomeza02
porthp-o20;30:8d:99:1d:f6:30;irenarr01
porthp-o21;30:8d:99:1c:ce:f4;lblancoa06
porthp-o21;30:8d:99:1c:ce:f4;mgarcias02
porthp-o22;30:8d:99:1a:dd:09;acarrerag27
porthp-o23;30:bd:23:22:8d:01;-
...
En este ejemplo anterior el portátil porthp-o20 sólo sería usable por el alumno irenarr01, el porthp-o21 estaría permitido a lblancoa06 y a mgarcias02, mientras que porthp-o23 podría ser usado por cualquier persona.

Lo mejor de todo: la cara de algunos alumnos cuando han venido a decirme que el portátil no les autorizaba el acceso, autoinculpándose de coger un portátil ajeno X-DDD...¿se puede ser más pardillo?

Montaje de recurso SAMBA falla en el arranque de los clientes Linux

Volvemos tras este prolongado silencio. Tengo varios recursos compartidos en un servidor samba que montan muchos equipos del centro en el arranque desde /etc/fstab con una línea como esta:
//192.168.0.99/media /media/media cifs _netdev,auto,user=user,password=password,rw,iocharset=iso8859-1,dir_mode=0777,file_mode=0777,vers=2.0
Durante años esto ha funcionado, pero tras unos cambios de ubicación de diversos recursos me he encontrado con que en algunas máquinas ya no se montaba la carpeta de forma automática en el inicio. Si una vez arrancada la máquina hacía:
# mount /media/media
Si se montaba sin problema. En el syslog veía que se producía un error de montaje en el inicio que daba a entender que se estaba intentando montar antes de estuviera disponible la red. Eso es absurdo ya que el parámetro _netdev de la línea de montaje le dice al sistema "hasta que no haya red, no montes". Pero no funciona, algo estaba fallando.

La solución adoptada es crear un fichero ejecutable en /etc/network/if-up.d/mountsmb con:
#!/bin/sh
mount -a
Esto ejecuta el script una vez que la tarjeta de red recibe IP y hay red disponible. En el script la línea "mount -a" fuerza el montaje de todo lo que hay en fstab marcado con el parámetro "auto". Y con eso se arregla el problema, aunque sigamos sin saber que lo originó.

Como dice el proverbio chino: siempre hay más soluciones que problemas.