Category: Programación

Linkero – Creación de APIs RESTful en Python (parte 1)

Según vas cogiendo experiencia como desarrollador de software te vas dando cuenta del verdadero significado de "No reinventar la rueda". Debido muchas veces a la falta de tiempo, es casi obligatorio buscar soluciones desde las que partir, o buscar integraciones de software que permitan alcanzar el objetivo marcado.

No obstante la mayoría se suele quedarse ahí y se contentan con reutilizar "piezas". El siguiente paso lógico y racional para ahorrar trabajo, pasa por diseñar. Por pensar y configurar un diseño que sea lo más generalista posible para que abarque el mayor número de casos de uso. Esta parte de diseño (y preferiblemente buen diseño) no suele ser frecuente entre desarrolladores, ya que suele implicar el gasto de más tiempo para ahorrarlo en un futuro. Debido a eso suele ser complicado trasladar a la dirección o al jefe inmediatamente superior y responsable del desarrollo, la necesidad de invertir más tiempo en un desarrollo para ahorrarlo en un futuro.

Hoy os traigo un caso de esos, en los que se juntan "piezas" y se refuerzan con un diseño para conformar una librería que ayude a un desarrollo más ágil, sujeto a un diseño flexible. Su nombre es Linkero:

https://github.com/RDCH106/linkero

link_slash

Linkero es un framework que permite la creación de manera sencilla de APIs RESTful para peticiones externas. Esta desarollado en Python y su diseño hace hincapié en los siguientes puntos:

  • Propósito general --> Se puede usar para cualquier tipo de API RESTful
  • Modular --> Se pueden agregar las API como si de módulos se tratasen quedando separadas del core de Linkero
  • Escalable --> Se pueden añadir nuevas APIs
  • Seguro --> Permite autenticar las peticiones por usuario y contraseña o mediante token de seguridad
  • Sencillo --> La codificación de las APIS y su securización es fácil de implementar.

La verdad es que originalmente el nombre de Linkero venía de link (unión en inglés) y el sufijo ero (que viene a significar "el  que"), es decir, el que une. Con el tiempo le vi una segunda connotación que tenía relación con el personaje Link de "The Lengedn of Zelda" y el juego de palabras link + hero (unión + héroe).

Como desde hace un tiempo, pienso que el Open Source es una vertiente totalmente Win&Win, decidí liberar el código fuente cuando consideré que el código estaba lo suficientemente maduro para empezar a recoger aportes. Uno de los mejores puntos que tiene el Open Source es su escrutinio público que ayuda a revelar fallos y la aportación de mejoras por otros usuario o empresas que usen dicho código y hayan implementado mejoras. Por contra, existe muchos usuario y sobre todo empresas que usan Open Source, pero no devuelven absolutamente NADA, si la licencia que acompaña el software lo permite. Es lícito hacerlo, no obstante la contrapartida real, es que tras cada mejora en el proyecto principal que se quiera integrar, resulta exponencialmente más difícil, ya que la mejoras introducidas no liberadas, pueden provocar divergencias con las actualizaciones y el diseño oficial.

Desde hace tiempo tengo la convicción de que en software, no se debería vender software tal cual, sino servicio, vender soluciones en vez de productos. Un mismo software o producto puede aplicarse para proveer diversidad de soluciones a problemas distintos e integrarse con otras tantas soluciones diferentes para solventar retos mayores. Esto es una visión totalmente personal ya que como Ingeniero Informático, lo he podido comprobar. El software es una herramienta. Yo proporciono soluciones a través de mi herramienta principal que es la creación de software y a su vez también uso software de terceros para proveer soluciones. Aclarada la visión personal y profesional de mi campo, volvamos al tema principal de la entrada 😉 .

Linkero cuenta con el siguiente diseño general:

linkero_general-scheme_800x600

Y hace uso de las siguientes librerías:

El esquema conceptual de dependencias refleja como únicas dependencias para crear una aplicación, el uso de Linkero y el uso de todas las APIs desarrolladas para Linkero que se quieran incluir. Linkero a su vez hace uso de las anteriormente citadas librerías. A su vez hace falta un "MAIN" que cargue las APIs y las ponga en funcionamiento con Linkero

Teniendo en mente el esquema puedes instalarte Linkero siguiendo las indicaciones de la Wiki:

https://github.com/RDCH106/linkero/wiki/instalacion

Si has seguido todos los pasos de la sección de instalación deberías poder probar Linkero usando el ejemplo testBasicAPI ejecutando el "testBasicAPI_main.py" desde Python.  Este ejemplo es una versión simplificada que a efectos ilustrativos no incluye temas de autenticación. Si analizamos el ejemplo, obtendremos la estructura mínima para desarrollar una API con Linkero que no requiera de autenticación.

Primero se importa el core de Linkero:

import core.linkero as linkero

A efectos demostrativos se crea una estructura de tareas pendientes:

# TODOS Data
TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}

Se define una función que maneje las peticiones sobre objetos no existentes y se genera un objeto "parser" para las peticiones, al que se le añade el argumento "task":

def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        linkero.abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = linkero.reqparse.RequestParser()
parser.add_argument('task')

Posteriormente definimos los métodos que nos permitan manejar los items de las tareas por separado (métodos GET, DELETE, PUT). Los métodos GET y DELETE hacen  uso del método "abort_if_todo_doesnt_exist" para comprobar que se puede realizar la acción sobre el item específico y PUT hace uso del objeto "parser" para obtener el valor a introducir de la petición:

# Todo
# shows a single todo item and lets you delete a todo item
class Todo(linkero.Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201

Es habitual ofrecer métodos que nos permitan manejar conjuntos de items. Por ello se definen métodos como pueden ser GET (obtener todas las tareas) o POST (incluir un grupo de tareas):

# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(linkero.Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

Una vez definida los métodos que permiten operar con la API, se define un método "loadTestBasicAPI" que asocia un patrón de URL a los métodos definidos:

##
## Actually setup the Api resource routing here
##
def loadTestBasicAPI():
    linkero.api.add_resource(TodoList, '/todos')
    linkero.api.add_resource(Todo, '/todos/<todo_id>')

Con la API completamente definida sólo queda cargarla y ejecutarla con Linkero mediante un "main.py" (para nuestro ejemplo es el "testBasicAPI_main.py"). Para ello se importa el core de Linkero y nuestra API (paso 1 y 2), se carga la API con el método "loadTestBasicAPI" (paso 3) y se invoca el método "run" de Linkero (paso 4):

# 1) Linkero Core
import core.linkero as linkero

# 2) APIs developed to use with Linkero
import examples.testBasicAPI

# 3) Load desired APIs
examples.testBasicAPI.loadTestBasicAPI()

# 4) Run Linkero
linkero.run()

Con estos simples pasos es posible realizar una API RESTful, definiendo la interfaz de la misma y su lógica en cómodos ficheros que pueden ser cargados y lanzados por Linkero. En la segunda parte de esta entrada se ahondará en la securización de la API, así como los aspectos que no se han podido tratar en esta primera parte.

OpenCV – Mouse Event Handler (parte 2)

Continuando la entrada "OpenCV - Mouse Event Handler (parte 1)", que ya publiqué hace un tiempo, os traigo la segunda parte que da cierre al manejo de eventos del ratón con OpenCV. Si en la primera parte me centraba en el manejo puro de los eventos del ratón, en esta entrada hablaré sobre la combinación de eventos de ratón con eventos de teclado.

opencv_mouse-event_display

La combinación de eventos de teclado con los eventos de ratón, son muy útiles por ejemplo cuando queremos asegurarnos que las acciones con el ratón no son fortuitas, y por ello se asegura la acción del ratón con la pulsación de alguna tecla. Un ejemplo común puede ser el pulsar la tecla "CRTL" o "SHIFT" cuando se hace click con el ratón. Otro ejemplo puede ser la de diferenciar dos acciones distintas desde un click de ratón, gracias a la pulsación combinada con distintas teclas.

El siguiente ejemplo toma como partida el ya expuesto en la entrada de la parte 1 y modifica la función "mouseEventHandler" de la siguiente forma:

void mouseEventHandler(int event, int x, int y, int flags, void* param)
{
     if ( flags == (EVENT_FLAG_CTRLKEY + EVENT_FLAG_LBUTTON) )
     {
          cout << "Left mouse button is clicked while pressing CTRL key - position (" << x << ", " << y << ")" << endl;
     }
     else if ( flags == (EVENT_FLAG_SHIFTKEY + EVENT_FLAG_RBUTTON) )
     {
          cout << "Right mouse button is clicked while pressing SHIFT key - position (" << x << ", " << y << ")" << endl;
     }
     else if ( event == EVENT_MOUSEMOVE && flags == EVENT_FLAG_ALTKEY)
     {
          cout << "Mouse is moved over the window while pressing ALT key - position (" << x << ", " << y << ")" << endl;
     }
}

Cada vez que se produzca alguno de los siguientes eventos:

  • EVENT_FLAG_LBUTTON
  • EVENT_FLAG_RBUTTON
  • EVENT_FLAG_MBUTTON
  • EVENT_FLAG_CTRLKEY
  • EVENT_FLAG_SHIFTKEY
  • EVENT_FLAG_ALTKEY

Se invocará a la función callback “mouseEventHandler“. En este caso, en la sucesión de los distintos “if” que permite identificar el tipo de evento, se incluye además  una condición de teclado. En el caso de los clicks izquierdo y derecho del ratón, se puede combinar las flags de pulsación del ratón con los del teclado, aplicando una suma aritmética de los valores. En el caso del movimiento del ratón, esto no es aplicable, ya que el movimiento del teclado es de tipo "EVENT" y el de la tecla es de tipo "EVENT_FLAG". A causa de ello se realiza una operación lógica entre los resultados obtenidos en las comparaciones de cada  tipo.

opencv_mouse-event_key-event_display

A continuación dejo el código completo de la arquitectura básica para el manejo de eventos con pulsaciones de teclado:

OpenCV_Mouse_Event_Handler_2.cpp

 

Mapeo persistente de dispositivos USB en Linux

Actualmente los sistemas operativos son capaces de detectar y montar dispositivos para poder ser usados por el usuario a través del sistema, haciendo que estos dispositivos conectados al sistema sean accedidos de manera transparente.

En Linux cuando conectamos cualquier dispositivo como un dispositivo USB, el sistema lo mapea automáticamente como un terminal en "/dev". El dispositivo aparece con el prefijo "tty" más un identificador del tipo de conexión que implementa a nivel de hardware el dispositivo (USB, ACM, S, ...) y un número que numera los distintos dispositivos del mismo tipo. Para ver los dispositivos que tiene conectados y mapeados en "/dev" ejecuta los siguientes comandos:

cd /dev   # Movernos a /dev
ls   # Listar elementos del directorio

Debido a que el nombre del mapeo de cualquier dispositivo depende de los dispositivos que haya conectados previamente al sistema operativo, puede resultar complicado automatizar procesos que dependen de dispositivos que pueden ser conectados y desconectados en caliente (mientras el sistema operativo está encendido). Para solucionar el problema vamos a realizar un re-mapeo sobre el mapeo automático que hace el sistema.

Lo primero que hay que identificar es el IDVENDOR y IDPROUDCT del dispositivo que queremos mapear de manera persistente. El IDVENDOR es un número que identifica al fabricante del dispositivo y el IDPRODUCT es otro número que identifica al producto del vendedor. Con estos dos valores, suele ser suficiente para mapear unívocamente dispositivos (a no ser que tengamos varios dispositivos idénticos conectados 🙁 , pero eso también tiene solución 😉 ). Para listar los dispositivos y ver ambos valores ejecuta "lsusb":

lsusb
# Result
Bus 001 Device 004: ID 0846:9041 NetGear, Inc. WNA1000M 802.11bgn [Realtek RTL8188CUS]
Bus 001 Device 005: ID 16d0:0856 MCS 
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Detectar cuál es el dispositivo es tan fácil como hacer "lsusb" antes de conectar el dispositivo  y volver a hacer "lsusb" con el dispositivo conectado. El valor del IDVENDOR es el valor comprendido entre el ID y los dos puntos, y el IDPRODUCT el valor comprendido entre los dos puntos y el primer espacio en blanco.

Si necesitamos mapear varios dispositivos de un mismo producto que son de un mismo fabricante, repetiremos el mismo proceso pero listando los puertos conectados y mapedados en "/dev" para conocer el mapeo que le asigna el sistema (ver primer snippet de código) y procederemos a ejecutar el siguiente comando para conocer el el valor SERIAL del dispositivo:

udevadm info -a -n /dev/ttyXXX | grep '{serial}' | head -n1
# Resultado
ATTRS{serial}=="XXXXXXXX"

Donde ttyXXX puede ser ttyUSB0, ttyUSB1, ttyUSB2 ..., ttyACM0, ttyACM1, ttyACM2 ..., ttyS0, ttyS1, ttyS2 ... El valor que nos devuelve de SERIAL es un identificador único para el dispositivo conectado.

Con esta información vamos a crear unas reglas para que al conectar el dispositivo nos realice el mapeo de manera persistente y estática de los dispositivos conectados. Para ello vamos a definir unas reglas UDEV en el siguiente directorio "/etc/udev/rules.d", creando el fichero "99-usb-serial.rules":

sudo nano 99-usb-serial.rules

El fichero lo crearemos con la siguiente estructura si queremos mapear un dispositivo de acuerdo a un un fabricante y producto:

SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="XXXX", SYMLINK+="nombre_mapeo_1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="XXXX", SYMLINK+="nombre_mapeo_2"
...

Si además necesitas diferencia diversos dispositivos de un mismo fabricante, añade el serial del dispositivo:

SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="XXXX", ATTRS{serial}=="XXXXXXXX", SYMLINK+="nombre_mapeo_1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="XXXX", ATTRS{serial}=="XXXXXXXX", SYMLINK+="nombre_mapeo_2"
...

Deberemos completar los valores "XXXX" con la información que hemos recogido en los pasos anteriores y guardamos haciendo CTRL+O y salimos con CTRL+X.

El fichero de reglas que hemos definido crea enlaces simbólicos que apuntan a las direcciones de acceso de los dispositivos y dichos enlaces simbólicos siempre apuntarán al dispositivo que hemos definido.

Si tras generar el fichero de reglas, no se realiza el mapeo que hemos definido, prueba conectar y desconectar el dispositivo, o a reiniciar el sistema. Os aparecerá en "/dev" un enlace simbólico con el nombre que hayáis definido en "SYMLINK+"

A la hora de usar estos mapeos desde scripts de Linux, debeís usar el comando:

readlink -f [symbolic_link]

De esta forma se obtiene la ruta completa a donde apunta el enlace simbólico y podremos programar scripts de manera más transparente, gracias al mapeo, el cual  nos generará un enlace simbólico persistente al dispositivo que conectemos.

 

Marcando estilo en tu Shell de Linux

En esta entrada os voy a explicar cómo personalizar vuestra Shell de Linux, pero antes de nada me gustaría aclarar ciertos conceptos que suelen confundirse y que son los siguientes:

  • Terminal = tty = Entorno de entrada y salida de texto
  • Console = Terminal físico
  • Shell = Intérprete de línea de comandos

Hablando en tiempos pretéritos, la forma de interactuar con computadoras Unix era mediante dispositivos teleprinter parecidos a máquinas de escribir, algunas veces llamados teletypewriter o "tty".

teletypewriter

El nombre de terminal viene del punto de vista de la electrónica y el nombre consola viene del punto de vista del mueble. En sistemas tipo Unix, tty es el tipo de fichero particular que implementa un número de comandos adicionales (ioctls) más allá de lectura y escritura. Terminal es asumido como un sinónimo de tty y algunas ttys son proveídas por el kernel del sistema operativo a favor de un dispositivo de hardware (como el teclado, modem o puertos seriales). Otras ttys, llamadas también pseudo ttys, son proveídas por programas llamados emuladores de terminal, como son Xterm, Screen, SSH...

Una consola generalmente es un terminal físicamente hablando, que hace referencia al primer terminal conectado a la compuradora. En algunos sistemas como Linux y FreeBSD, la consola dispone de múltiples ttys que pueden ser seleccionados mediante la combinación de teclas:

CTRL+ALT+[F1 ... F12]

Siendo cada uno de los Fx desde F1 a F12 los distintos tty disponibles en el sistema.

Un shell es la interfaz principal que los usuarios ven cuando inician sesión, y cuyo propósito es iniciar otros programas. En sistemas Unix, shell hace alusión a la línea de comandos, cuya finalidad es recibir nombres de aplicaciones y los ficheros sobre los cuales actúa la misma, al presionar la tecla "enter".

Probablemente la shell más popular para Linux sea Bash, basado en la sintaxis de Bourne Shell, con el que se pueden realizar programas usando instrucciones Bash de cierta complejidad, con una estructura muy parecida a otros lenguajes de alto nivel.

Aclarados los conceptos base, vamos a proceder a dar un estilo propio al inicio de sesión de nuestra shell de Linux. La forma más sencilla de hacerlo es personalizando el fichero "/etc/motd" (Message Of The Day). Para ello ejecutamos el siguiente comando:

sudo nano motd

Se nos abrirá o creara el fichero con permisos de usuario. Dentro deberemos poner en texto plano lo que queramos que aparezca, guardar haciendo CTRL+O y salir con CTRL+X. Si andas falto de ideas puedes entrar a este generador de texto ASCII:

http://patorjk.com/software/taag

O a esta galería de imágenes ASCII:

http://www.chris.com/ascii/joan/www.geocities.com/SoHo/7373/index.html

Cuando guardes el fichero, prueba cómo ha quedado abriendo otro terminal y ejecutando el siguiente, le cual abre una sesión ssh de tu usuario contra tu propio equipo:

ssh 127.0.0.1

Pedirá la contraseña de usuario (la introduces) y verás el mensaje. Si no aparece, prueba a salir de la sesión ssh con "exit" y prueba a ejecutar el comando anterior de nuevo. Deberías entonces poder ver tu mensaje.

custom-motd

El MOTD de mi ejemplo podéis encontrarlo en:

https://gist.github.com/RDCH106/876cdb98e7d1b0f3ef22040213a48c40

Si lo que quieres es un mensaje del día más elaborado que no consista en hacer "print" de un texto plano, entramos en "/etc/update-motd.d" y listamos los ficheros con "ls":

update-motd

Se trata de una serie de scripts Bash que poseen una sintaxis especial en el nombre del fichero. Los números que preceden al nombre del fichero, indican el orden en que se ejecutan cuando se inicia una sesión, siendo los números menores los que se ejecutan antes. Estos ficheros a diferencia del "motd" contienen comandos que son ejecutados por el super-usuario "root" y se trata por tanto de ficheros interpretados.

Vamos a crear uno con nombre "20-stats" en el que mostraremos algunas estadísticas del sistema:

sudo nano 20-stats

Y copiamos el siguiente código:

#!/bin/bash

function color (){
  echo "\e[$1m$2\e[0m"
}

function extend (){
  local str="$1"
  let spaces=60-${#1}
  while [ $spaces -gt 0 ]; do
    str="$str "
    let spaces=spaces-1
  done
  echo "$str"
}

function sec2time (){
  local input=$1
  
  if [ $input -lt 60 ]; then
    echo "$input seconds"
  else
    ((days=input/86400))
    ((input=input%86400))
    ((hours=input/3600))
    ((input=input%3600))
    ((mins=input/60))
    
    local daysPlural="s"
    local hoursPlural="s"
    local minsPlural="s"
    
    if [ $days -eq 1 ]; then
      daysPlural=""
    fi
    
    if [ $hours -eq 1 ]; then
      hoursPlural=""
    fi
    
    if [ $mins -eq 1 ]; then
      minsPlural=""
    fi
    
    echo "$days day$daysPlural, $hours hour$hoursPlural, $mins minute$minsPlural"
  fi
}

borderColor=35
headerLeafColor=32
headerRaspberryColor=31
greetingsColor=36
statsLabelColor=33

borderLine="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
borderTopLine=$(color $borderColor "┏$borderLine┓")
borderBottomLine=$(color $borderColor "┗$borderLine┛")
borderBar=$(color $borderColor "┃")
borderEmptyLine="$borderBar                                                                              $borderBar"

header="$borderTopLine"

uptime="$(sec2time $(cut -d "." -f 1 /proc/uptime))"
uptime="$uptime ($(date -d "@"$(grep btime /proc/stat | cut -d " " -f 2) +"%d-%m-%Y %H:%M:%S"))"

label1="$(extend "$uptime")"
label1="$borderBar  $(color $statsLabelColor "Uptime........:") $label1$borderBar"

label2="$(extend "$(free -m | awk 'NR==2 { printf "Total: %sMB, Used: %sMB, Free: %sMB",$2,$3,$4; }')")"
label2="$borderBar  $(color $statsLabelColor "Memory........:") $label2$borderBar"

label3="$(extend "$(df -h ~ | awk 'NR==2 { printf "Total: %sB, Used: %sB, Free: %sB",$2,$3,$4; }')")"
label3="$borderBar  $(color $statsLabelColor "Home space....:") $label3$borderBar"

stats="$label1\n$label2\n$label3"

# Print motd
echo -e "$header\n$borderEmptyLine\n$stats\n$borderEmptyLine\n$borderBottomLine" 

Guarda haciendo CTRL+O y salir con CTRL+X.

Además vamos a añadir otro script más para personalizar el inicio de sesión del perfil, que lo haremos en "/etc/profile.d" . La razón de hacerlo en profile es debido a que los mensajes MOTD se ejecutan por el root y por ejemplo si hacemos un "whoami" nos devolverá siempre root. Para ello creamos un script "wellcome.sh" que nos saludará y mostrará la fecha y hora:

sudo nano wellcome.sh

Y copiamos el siguiente código:

#!/bin/bash

function color (){
  echo "\e[$1m$2\e[0m"
}

function extend (){
  local str="$1"
  let spaces=60-${#1}
  while [ $spaces -gt 0 ]; do
    str="$str "
    let spaces=spaces-1
  done
  echo "$str"
}

function center (){
  local str="$1"
  let spacesLeft=(78-${#1})/2
  let spacesRight=78-spacesLeft-${#1}
  while [ $spacesLeft -gt 0 ]; do
    str=" $str"
    let spacesLeft=spacesLeft-1
  done
  
  while [ $spacesRight -gt 0 ]; do
    str="$str "
    let spacesRight=spacesRight-1
  done
  
  echo "$str"
}

borderColor=35
headerLeafColor=32
headerRaspberryColor=31
greetingsColor=36
statsLabelColor=33

borderLine="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
borderTopLine=$(color $borderColor "┏$borderLine┓")
borderBottomLine=$(color $borderColor "┗$borderLine┛")
borderBar=$(color $borderColor "┃")
borderEmptyLine="$borderBar                                                                              $borderBar"

me=$(whoami)

header="$borderTopLine"

# Greetings
greetings="$borderBar$(color $greetingsColor "$(center "Welcome back, $me!")")$borderBar\n"
greetings="$greetings$borderBar$(color $greetingsColor "$(center "$(date +"%A, %d %B %Y, %T")")")$borderBar"

# Print motd
echo -e "$header\n$borderEmptyLine\n$greetings\n$borderEmptyLine\n$borderBottomLine" 

Guarda haciendo CTRL+O y salir con CTRL+X.

Abrimos una sesión ssh de tu usuario contra tu propio equipo, como ya hemos hecho anteriormente, y veremos un inicio de sesión como el siguiente, pero con los datos de tu sesión y equipo.

custom-motd_2

La finalidad de estos MOTD y scripts de perfil, pueden servir para la generación de mensajes informativos concretos para los usuarios, la generación de paneles informativos del sistema para administradores o simplemente para personalizar/corporativizar un sistema Linux.

img_20160414_174706.jpg

Espero que este artículo os haya servido para aprender a explotar estas características tan útiles del inicio de sesión de Linux.