Català   English  

Cloudy plug-ins

De Guifi.net - Wiki Hispano

¿Qué es un plug-in?

Cloudy es una distribución de Linux basada en Debian que proporciona a los usuarios una interfaz simple y cómoda para gestionar diferentes servicios que pueden usarse a través de una red comunitaria; en nuestro caso, Guifi.

Al mismo tiempo, Cloudy ofrece una serie de herramientas que permiten a un usuario añadir un servicio de su agrado sin tener grandes conocimientos sobre redes e informática en general. Automaticamente, estos servicios se publican mediante Avahi, de manera que todo el que utilice Cloudy podrá hacer uso de estos servicios.

Otra manera de llamar estos servicios que se pueden poner y quitar de forma sencilla es plug-in. Por tanto, entendemos por plug-in a un programa o software que un usuario quiere añadir a la distribución y que puede ser gestionado de forma simple des de la interfaz web que provee Cloudy.


Añadir servicios a Cloudy

Una pequeña introducción

Esta guía te enseñará el proceso para añadir un nuevo servicio en la distribución Cloudy.

Para serclaro y dar algunos ejemplos al mismo tiempo, integraremos un servicio bastante simple mostrando algunos trozos de código por el camino. Para seguir el tutorial con más facilidad o bien para integrar servicios más complejos, estaría bien tener algunos conocimientos de Bash[1] y de PHP[2].

Por simplicidad utilizaremos Pastecat[3]. Es un buen candidato ya que:

  • Es independiente y no se federa ni se comunica con otros nodos.
  • Está hecho en Go, es facil de distribuir e instalar.
  • No necesita archivos de configuración.
  • No necesita muchos recursos.

Obtener el binario

Lo primero que hemos de investigar es como descargar e instalar el binario en Cloudy. La mayoria de software que podemos encontrar está por defecto en los paquetes de Debian, pero Pastecat no está. Si se diera el caso de que estuviera, solo tendríamos que ejecutar el comando apt-get install pastecat desde PHP.

Pero como este no es el caso, tendremos que obtener el binario por otro lado. Una opcion es coger el código fuente y compilarlo nosotros mismos, pero el problema de esto es que Cloudy necesitaria incluir muchas herramientas y librerias de desarrollo, y este no es el objetivo.

La mejor opción si un paquete de Debian no está disponible en los repositorios es descargar los binarios de otro lugar seguro. Podemos utilizar el servicio de "releases" de Github para ello. Las dos opciones nos dan un archivo ejecutable que tendríamos que ser capaces de ejecutar directamente sobre Cloudy.

En este caso en particular descargaremos los binarios desde el repositorio git con la siguiente linea de comandos:

   wget https://github.com/mvdan/pastecat/releases/download/v0.3.0/pastecat_linux_386

Nótese que en este caso en concreto estamos descargando una versión específica para Linux con arquitectura i386.

Fijaos que tener un servicio en los paquetes de Debian tiene muchas ventajas:

  • Las actualizaciones son simples y no necesitan trabajo extra desde Cloudy.
  • El paquete está compilado para Debian de manera segura.
  • Los archivos de init.d ya vienen por defecto.
  • Los paquetes de Debian suelen contener correciones.

Probarlo

Antes de añadir un servicio deberíamos configurarlo y probarlo nosotros directamente para ver cómo funciona y que realmente funciona. También hemos de entender qué opciones de configuración o de linea de comandos necesitamos para hacer uso de este servicio en particular y como lo gestionariamos una vez este funcionando.

Añadir el controlador

En web/plug/controllers tenenmos un archivo PHP para cada servicio, llamado controlador (o 'controller' en inglés). Este archivo contiene el codigo que se ejecutará cuando se visite la página de nuestro servicio desde la interfaz de Cloudy.

Añadir la función indice

Lo que queremos es que nuestro servicio esté integrado en la estructura web de Cloudy. Para ello, necesitamos crear unos cuantos scripts en PHP y añadirlos a nuestro sistema. De momento crearemos dos scripts: pastecat.php y pastecat.menu.php. El primer archivo es el controlador, es decir, el script que renderiza la página y tiene toda la información tal como botones o redireccionamientos. El otro es el que permite que nuestro servicio se muestre en los menus superiores desplegables de Cloudy.

El código del menú será similar a esto:

   <?php
   //peerstreamer.menu.php
   addMenu('Pastecat','pastecat','Clommunity');

Por ahora, utilizaremos un script muy simple en PHP como controlador:

   <?php
   //pastecat
   $title="Pastecat";
   
   function index(){
       global $paspath,$title;
       global $staticFile;
   
       $page=hlc(t($title));
       $page .= hl(t("Minimalist pastebin engine written in Go"),4);
       $page .= par(t("A simple and self-hosted pastebin service written in Go").' '.t("Can use a variety of storage backends").' '.t(" Designed to optionally remove pastes after a certain period of time.").' '.("If using a persistent storage backend, pastes will be kept between runs.").' '.t("This software runs the").' '."<a href='http://paste.cat'>".t("paste.cat")."</a>". t(" public service."));
       
       return(array('type' => 'render','page' => $page));
   }

En nuestro sistema Cloudy estos archivos han de ponerse en los directorios dentro de /var/local/cDistro/plug/. El primero ha de ir en menus y el segundo en el directori controllers. Una vez hecho esto podemos ir a la web de nuestro Cloudy y acceder al nuevo menú Pastecat.

Hacer que el controlador instale nuestro servicio

Como se ha comentado anteriormente, este paso es mucho más sencillo de hacer si el servicio está entre los paquetes de Debian. Como que no es el caso de Pastecat, lo tendremos que hacer manualmente. Normalmente hacer este proceso manualmente incluye una combinación de los comandos wget, mv y chmod. En general, es buena idea mantener los archivos de este tipo de servicios en /opt/SERVICENAME.

En nuestro caso particular, lo primero que necesitamos es descargar el binario de la release de Github. Para ello utilizaremos el comando: wget. Dada una URL a un archivo, este comando permite descargar este archivo en nuestro sistema, i es lo que haremos con:

   wget https://github.com/mvdan/pastecat/releases/download/v0.3.0/pastecat_linux_386

Una vez tengamos el binario en nuestra máquina, lo moveremos al directorio /opt/pastecat/. Para mover los archivos por nuestro sistema usaremos el comando mv. Aun así, primero tendremos que crear el directorio donde pondremos el archivo. Para ello utilizaremos mkdir de la siguiente manera:

  mkdir -p /opt/pastecat/

Una vez hayamos creado el directorio, es hora de mover el binario:

   mv current_directory/pastecat_linux_386 /opt/pastecat/

Donde "current_directory" es el directorio donde previamente hemos descargado el binario. Como el nombre del binario depende de la arquitectura, para simplificar el código del controlador, cambiaremos el nombre del archivo ejecutable por algo más genérico y simple:

   mv /opt/pastecat/pastecat_linux_386 /opt/pastecat/pastecat

Ahora nuestro binario se llama pastecat en lugar de pastecat_linux_386.

Estos pasos son los requisitos mínimos para instalar un servicio que no se ofrece directamente en los repositorios oficiales de Debian. Sin embargo, de cara a un usuario final, sería una pesadilla tener que hacer todos estos comandos en una consola conectado a través de ssh a su dispositivo, así que lo que haremos a continuación es crear un script en Bash que será llamado más adelante a traves de la interfaz web haciendo click en un botón.

Este script es la primera versión del controlador de Pastecat. Por ahora solo incluiremos una función para instalar Pastecat en nuestro dispositivo. Más adelante incluiremos otras funciones para proveer a nuestro script de más servicios:

   #!/bin/bash
   PCPATH="/opt/pastecat/"
   
   doInstall() {
       if isInstall
       then
           echo "Pastecat is already installed."
           return
       fi
   
       # Creating directory and switching
       mkdir -p $pcpath && cd $pcpath
   
       # Getting file
       wget https://github.com/mvdan/pastecat/releases/download/v0.3.0/pastecat_linux_386
   
       # Changing name so controller can invoke it generically
       mv pastecat_linux_386 pastecat
       chmod +x pastecat
       
       cd -
   }
   
   isInstalled() {
       [ -d $pcpath ] && return 0
       return 1
   }
   
   
   case $1 in
       "install")
           shift
           doInstall $@
           ;;
   esac

Podemos ver como los últimos pasos se han concentrado en una misma función, permmiténdonos instalar el software en el dispositivo de una manera mas sencilla.

Hacer que el controlador utilitce Pastecat

Ejecutar el servicio

El paso siguiente es hacer que nuestro software se pueda utilizar desde la intarfaz web. Para ello incluiremos una nueva opción en nuestra página principal de Pastecat e incluiremos una nueva función en nuestro controlador para gestionar el binario. Añadiremos un botón en el código PHP de la siguiente manera:

   $page .= addButton(array('label'=>t('Create a Pastecat server'),'href'=>$staticFile.'/pastecat/publish'));

Ha de ir justo después del mensaje Pastecat is installed. A continuación se ha de implementar la función publish en el mismo PHP. Esta función es la responsable de llamar a la función correspondiente desde el controlador y anunciar nuestro servicio utilizando Avahi. La diferencia con esta función es que requiere un formulario para introducir datos, así que al final acabaremos teniendo dos funciones: una get i una post:

   function publish_get() {
       global $pcpath,$title;
       global $staticFile;
       
       $page = hlc(t($title));
       $page .= hlc(t('Publish a pastecat server'),2);
       $page .= par(t("Write the port to publish your Pastecat service"));
       $page .= createForm(array('class'=>'form-horizontal'));
       $page .= addInput('description',t('Describe this server'));
       $page .= addSubmit(array('label'=>t('Publish'),'class'=>'btn btn-primary'));
       $page .= addButton(array('label'=>t('Cancel'),'href'=>$staticFile.'/peerstreamer'));
       
       return(array('type' => 'render','page' => $page));
   }
   
   function publish_post() {
       $port = $_POST['port'];
       $description = $_POST['description'];
       $ip = "";
       
        $page = "<pre>";
        $page .= _pcsource($description);
        $page .= "</pre>";
       
       return(array('type' => 'render','page' => $page));
   }

Como podemos ver en la función post estamos invocando otra función. El motivo de hacer esto es escribir el código de manera más simple y modular. En esta última funcion por fin llamamos al script:

   function _pcsource($port,$description) {
       global $pcpath,$pcprogram,$title,$pcutils,$avahi_type,$port;
   
       $page = "";
       $device = getCommunityDev()['output'][0];
       $ipserver = getCommunityIP()['output'][0];
   
       if ($description == "") $description = $type;
   
       $cmd = $pcutils." publish '$port' '$description';
       execute_program_detached($cmd);
   
       $page .= t($ipserver);
       $page .= par(t('Published this server.'));
   
       $page .= addButton(array('label'=>t('Back'),'href'=>$staticFile.'/pastecat'));
   
       return($page)
   }

Lo siguiente que hemos de hacer es crear una función publish en el controlador, por lo que añadiremos una nueva función al controlador básico que tenemos en la sección 3.2. Primero añadiremos un nuevo paámetro llamado publish, asi que la primera parte en ejecutarse del script sera:

   if [ $# -lt 1 ]
   then
       doHelp
   fi
   
   case $1 in
       "install")
           shift
           doInstall $@
           ;;
       "publish")
           shift
           doServer $@
           ;;
   esac

Como se puede ver, cuando el primer argumento del script es publish, desplazamos (shift) el resto de argumentos y llamamos a la funcion doServer. En esta función hemos de poner en marcha el servicio con los argumentos correspondientes, así que lo primero queharemos será poner estos parámetros en variables locales. Una vez hecho esto lo más lógico sería lanzar la aplicación directamente, pero como el script se llama con permisos de "root" (no recomendable), hemos de lanzar Pastecat como usuario nobody. El problema es que el usuario nobody no tiene casi permisos y Pastecat necesita algunos persmisos para crear directorios y archivos de texto. Para permitir que nobody pueda hacerlo, primero crearemos un directorio con permisos a casi todos los usuarios. Para esto, utilizaremos el comando chmod de nuevo. Ahora el usuario puede crear archivos y directorios dentro de este mismo directorio, y en consecuencia podemos ejecutar Pastecat. Finalmente hay que mantener el PID en una variable en caso de que lo necesitemos más adelante (que ya os adelantamos que sí lo necesitaremos):

   doServer() {
       # Turning machine into a server
   
       local port=${1:-""}
       local description=${2:-""}
       local ip=${3:-"0.0.0.0"}
   
       # Creating directory with nobody permissions
       mkdir -p "/var/local/pastecat"
       chmod 777 "/var/local/pastecat" && cd "/var/local/pastecat"
   
       # Running pastecat 
       cmd='su '$PCUSER' -c "{ '$PCPATH$PCPROG' -l :'$port' > '$LOGFILE' 2>&1 & }; echo \$!"'
       pidpc=$(eval $cmd)          # keeping PID for future needs...
   
       cd -
   
       # Using the PID, we could carry on process control so if the pastecat process die, we can also
       # stop the avahi process to avoid "false connections"
   
       return
   }

Fijaos que ahora estamos utilizando algunas variables globales que no estaban definidas antes, como PCUSER y LOGFILE. Estas variables tienen los siguientes valores:


   PCPATH="/opt/pastecat/"
   PCPROG="pastecat"
   LOGFILE="/dev/null"
   PCUSER="nobody

Parar el servicio

En algún momento es posible que queramos parar nuestro servicio. Para esto crearemos un botón para hacerlo. Lo primero que hacer sería crear el botón, pero si pensamos un poco veremos que antes de crearlo necesitamos una manera de saber si nuestro servicio esta ejecutándose o no. Además, también necesitamos una manera de parar nuestro servicio. Como estamos ejecutando sobre Linux, podemos parar el servicio fácilmente utilizando el comando kill. El problema es que para utilizar este comando primero necesitamos saber el número de identificación del proceso (PID). Por suerte, ja nos quedamos con este número cuando creamos el servidor de Pastecat; en concreto con la linea pidpc=$(eval $cmd).

Ahora que tenemos todo lo que necesitamos para matar nuestro proceso vamos a ver como lo hacemos para que PHP pueda saber si el servicio está ejecutándose o no. Una manera fácil y eficiente de hacerlo es almacenando algunos datos útiles en un archivo y borrarlo cuando se pare Pastecat. De esta manera nos aseguramos que el archivo sólo existe cuando Pastecat está activo. Este archivo lo crearemos desde el controlador aádiendo las siguientes lineas justo despues de la sentencia que hemos mencionado en el párafo anterior:

   # Writting server info to file
   info="$pidpc http://$ip:$port"          # Separator is space character
   echo $info > $PCFILE

Donde $PCFILE es una variable global que contiene /var/run/pc.info. Fijaos que el contenido del archivo sera el PID del Pastecat y la dirección completa del servidor de este.

Ahora ya tenemos una manera de saber si el servicio está activo o no, de manera que añadiremos justo a continuación el botón 'stop' en la interfaz web. Modificaremos un poco el script PHP que teniamos antes, añadiendo un anuncio indicando si Pastecat se está ejecutando o no, y dos botones más cuando éste esté ejecutándose. Así pues, nuestra función índice contendrá el siguiente codigo dentro de la condición que comprueba si Pastecat está instalado o no:

    $page .= "<div class='alert alert-success text-center'>".t("Pastecat is installed")."</div>\n";
    if ( isRunning() ) {
        $page .= "<div class='alert alert-success text-center'>".t("Pastecat is running")."</div>\n";
        $page .= addButton(array('label'=>t('Go to server'),'href'=>'http://'. getCommunityIP()['output'][0] .':'. $port));
        $page .= addButton(array('label'=>t('Stop server'),'href'=>$staticFile.'/pastecat/stop'));
    } else  {
        $page .= "<div class='alert alert-error text-center'>".t("Pastecat is not running")."</div>\n";
    }
    $page .= addButton(array('label'=>t('Create a Pastecat server'),'href'=>$staticFile.'/pastecat/publish'));

En este trozo de código podemos apreciar dos nuevas cosas. La priemra es una función de comprobación llamada isRunning(). Esta función se parece mucho a la funcion que utilizamos para comprar si Pastecat esta instalado o no:

   function isRunning() {
       // Returns whether pastecat is running or not
       global $pcfile;
       
       return(file_exists($pcfile));   
   }

Es tan simple como parece, solamente comprueba que el archivo creado cuando se lanza el servidor de Pastecat aun existe. Lo segundo que podemos ver nuevo en nuestro codigo es la existencia de una nueva función llamada stop. Esta funcion invocará otra función en el controlador que parará el servidor de Pastecat:

   function stop() {
       // Stops Pastecat server
       global $pcpath,$pcprogram,$title,$pcutils,$avahi_type,$port;
       
       $page = "";
       $cmd = $pcutils." stop ";
       execute_program_detached($cmd);
       
       return(array('type'=>'redirect','url'=>$staticFile.'/pastecat'));
   }

Para hacer que el controlador entienda esta orden tendremos que modificarla sentencia case y añadir la nueva función. En el case añadiremos lo siguiente debajo de la opción install:

   "stop")
       shift
       doStop $@
       ;;

Esto llama a la función doStop dentro del mismo controlador. Esta función será similar a:

   doStop() {
       # Stopping pastecat server
       pcpid=$(cat $PCFILE | cut -d' ' -f1)
       kill $pcpid
       
       # Removing info file
       rm $PCFILE
   }

Esta función coge el PID de Pastecat del archivo que hemos creado antes, mata el proceso y finalmente borra el archivo de manera que PHP pueda detectar que Pastecat se ha parado.

Ahora podemos crear un servidor de Pastecat y pararlo. Aun así, aun hay algo que falta: hacer que el resto de usuarios puedan ver nuestro servicio. Para esto usaremos Avahi.

Publicación de servicios Avahi

Una de las mejores cosas de Cloudy es la caracteristica de publicar nuestros servicios con Avahi, permitiendo a otros usuarios saber qué ofrecemos y que puedan unirse a nuestro servicio. Para hacer esto primero hemos de añadir algunas lineas de código en el controlador PHP, justo debajo de la llamada al controlador en Bash para activar el servidor Pastecat. Añadiremos las siguientes lineas:

   $description = str_replace(' ', , $description);
   $temp = avahi_publish($avahi_type, $description, $port, "");
   $page .= ptxt($temp);

Aisí al final nuestra función quedará así:

   function _pcsource($port,$description) {
       global $pcpath,$pcprogram,$title,$pcutils,$avahi_type;
       
       $page = "";
       $device = getCommunityDev()['output'][0];
       $ipserver = getCommunityIP()['output'][0];
       
       if ($description == "") $description = $type;
       
       $cmd = $pcutils." publish '$port' '$description'";
       execute_program_detached($cmd);
       
       $page .= t($ipserver);
       $page .= par(t('Published this server.'));
       $description = str_replace(' ', , $description);
       $temp = avahi_publish($avahi_type, $description, $port, "");
       $page .= ptxt($temp);
       
       $page .= addButton(array('label'=>t('Back'),'href'=>$staticFile.'/pastecat'));
       
       return($page)
   }

Con este simple paso hemos anunciado nuestro servicio en la red de Avahi. Pero el trabajo no acaba aquí, queda aun una cosa mas por hacer: crear un botón y programarlo de manera que cuando se pulse nos redirija directamente a nuestro servidor de Pastecat.

Para hacer esto hay una carpeta llamada avahi dentro del directorio plug. Los scripts que definen la acción a llevar a cabo cuando se pulsan los botones estan en diferentes archivos dentro de este directorio. De este modo, crearemos un archivo llamado pastecat.avahi.php que contendrá:

   <?php
   // plug/avahi/pastecat.avahi.php
   
   addAvahi('pastecat','fpcserver');
   
   function fpcserver($dates){
       global $staticFile;
       
       return ("<a class='btn' href='http://" .$dates['ip'] .":". $dates['port']."'>Go to server</a>  ");
   }

Esto creará un botón al lado del anuncio de Avahi que apuntará a nuestro servidor.

Ahora que ya tenemos nuestro servicio anunciado, queremos que este desaparezca una vez paremos el servidor de Pastecat. Este último paso es muy simple pero importante. Consiste en unas pocas lineas de código en la función 'stop' del script PHP. Hasta ahora, esta función solo llamaba al controlador Bash y paraba Pastecat, pero ahora también parará la publicación Avavhi y mostrará un breve comentario notificando que la llamada ha funcionado:

   $temp = avahi_unpublish($avahi_type, $port);
   $flash = ptxt($temp);
   setFlash($flash);

Estas lineas tienen que ser añadidas justo después de la sentencia execute_program_detached($cmd) en la función 'stop' de PHP.

Final

Finalmente añadir que todos los archivos implementados en este tutorial se pueden encontrar en un repositorio Github, que aparece en el apartado de Enlaces Externos, junto a este mismo tutorial en formato Markdown (md).


Referencias

  1. (http://www.tldp.org/LDP/Bash-Beginners-Guide/Bash-Beginners-Guide.pdf)
  2. (https://php.net/manual/es/index.php)
  3. (https://github.com/mvdan/pastecat)


Enlaces externos

https://github.com/Clommunity/Doc/tree/master/plugins/pastecat

Herramientas personales