Tutorial: Traducciones rápidas usando CSS para el plugin Opal Estate Pro

Continuando con mi aprendizaje de CSS, esta vez he encontrado que necesito cambiar el idioma de los controles generados por el plugin Opal Estate Pro, un plugin para integrar un sistema inmobiliario completo en WordPress, que puedes encontrar en este enlace.

La verdad, este plugin es muy interesante y ofrece muchas funciones, pero su documentación solo se limita a su pagina web y su soporte es solo en inglés. Por esta razón, mientras trabajaba en un proyecto, encontré la necesidad de hacer una traducción rapida hasta que encontraba como traducir los controles o encontrar un plugin en español.

El problema

Opal Estate Pro solo tiene soporte en inglés y el cliente quería los textos de los controles en español. En un principio pensé en usar el plugin Polylang, pero no funcionó. Como el ´presupuesto no alcanzaba para buscar una mejor alternativa, opté por usar CSS para cambiar el contenido de las etiquetas «label» mediante el siguiente código:

#a{
   content:"nuevocontenido";
}

Esta código permite agregar contenido a la etiqueta, por lo que nuestro texto se agregará al que ya estaba presente. para corregir esto, se aplica el siguiente estilo.

#a{
   visibility:hidden;
}
#a::before{
   content:"nuevocontenido";
   visibility:visible;
}

Al aplicar este estilo, se oculta el contenido anterior de la etiqueta y se agrega el nuevo contenido, dejando solo visible este ultimo.

Aprovechando que Opal Estate tiene clases especificas para cada uno de sus controles, podemos aprovechar esto para crear nuevas clases que aplicaremos en el CSS adicional del tema que tengamos activo.

/*Cambiar el idioma del label "Bedrooms" a español*/
.opalestate-label--Bedrooms {
    visibility: hidden;
}
.opalestate-label--Bedrooms::before {
    content: "Habitaciones";
    visibility: visible;
}
/*Cambiar el idioma del label "Parking" a español*/
.opalestate-label--Parking {
    visibility: hidden;
}
.opalestate-label--Parking::before {
    content: "Parqueo";
    visibility: visible;
}
/*Cambiar el idioma del label "Country" a español*/
.opalestate-label--country {
    visibility: hidden;
}
.opalestate-label--country::before {
    content: "País";
    visibility: visible;
}
/*Cambiar el idioma del label "Bathroom" a español*/
.opalestate-label--Bathrooms {
    visibility: hidden;
}
.opalestate-label--Bathrooms::before {
    content: "Baños";
    visibility: visible;
}
/*Cambiar el idioma del label "Type" a español*/
.opalestate-label--type {
    visibility: hidden;
}
.opalestate-label--type::before {
    content: "Tipo";
    visibility: visible;
}
/*Cambiar el idioma del label "Price" a español*/
.opalestate-label--price {
    visibility: hidden;
}
.opalestate-label--price::before {
    content: "Precio";
    visibility: visible;
}

De esta manera, conseguiremos nuestra traducción rápida para casos de emergencia. Por el momento, solo tengo estos controles traducidos y el código no funciona con el botón de búsqueda, así que toca investigar mas para llegar a un resultado completo. Por el momento, esto ha funcionado lo suficientemente bien como para dejarlo aplicado en producción.

Tutorial: Resaltar las respuestas correctas en Moodle

A medida que se ha ido modernizando los métodos de enseñanzas, he encontrado diferentes LMS (Learning Management System) como Chamilio, Lifter LMS y Moodle, donde este último es el más frecuente de todos. Cada uno tiene sus fortalezas y debilidades, pero he elegido Moodle para mis actividades de enseñanza debido a su modularidad y escalabilidad. Esto significa también que he encontrado problemas de diferentes magnitudes donde es necesario intervenir un poco en el código del sistema.

El problema que resolveremos hoy, probablemente pase desapercibido por muchos, pero es uno relacionado a la accesibilidad. Algunos temas lo resuelve, pero el predeterminado y algunos otros carecen de esta opcion.

Al hacer un cuestionario, moodle da la opción de marcar las respuestas correctas e incorrectas, destacando con tan solo un check los resultados. Una mejora de accesibilidad sería el resaltar toda la fila, puesto que esto hace más fácil la lectura. para esto, tenemos que hacer lo siguiente:

Buscamos en la administración del sitio, en la subsección de apariencia, el tema que tengamos activo. En este caso, mi tema activo es el predeterminado para mi version, llamado Boost.

Dentro del tema, vamos a los ajustes avanzados y agregamos en «SCSS sin modificar» agregamos el siguiente selector de cascada:

.answer .correct{
    background-color: mediumspringgreen;
    border-radius: 35px;
}

Y listo, tenemos resaltado la respuesta correcta con un bonito color verde claro. Eso sí, resaltar la respuesta incorrecta requiere algo más elaborado, pero lo dejaré para la próxima ocasión.

Resaltado de respuesta correcta en Moodle
Resaltado de respuesta correcta en Moodle

Tutorial: Solución para problemas de actualización en NextCloud

NextCloud es una plataforma de almacenamiento en la nube basada en PHP que por muchas situaciones, incluido el ser un fork de OwnCloud, han causado que tenga una comunidad mas pequeña de lo que merece, por lo que es posible encontrarse problemas básicos, que requieren mayor investigación y conocimiento técnico para resolverlos.

Al ser una solución SelfHosted, es normal encontrarse instancias de NextCloud en Raspberry Pi o computadoras de bajos recursos. Esto incide directamente en situaciones que no se darían al ser administrados de forma profesional, como es el caso del error que vamos a revisar hoy.

Según la Wikipedia:

Nextcloud es una serie de programas cliente-servidor que permiten la creación de servicios de alojamiento de archivos. Su funcionalidad es similar al software Dropbox, aunque Nextcloud es en su totalidad software de código abierto. Nextcloud permite a los usuarios crear servidores privados. Su modelo de desarrollo abierto permite añadir y/o modificar la funcionalidad del software del servidor en forma de aplicaciones. Nextcloud es una bifurcación de ownCloud, que también es un software de servicio de alojamiento en la nube

Nextcloud en la Wikipedia. Consultado al 2/12/2022

El problema

Hasta el momento no he encontrado una explicación razonable para este problema, pero ha aparecido en todas mis instalaciones hasta el momento según las siguientes configuraciones:

  1. VPS 1GB de RAM
    • PHP + Apache + NGINX + Subdominio
    • PHP + Apache + Subdominio
    • PHP + NGINX + Subdominio
  2. Raspberry Pi Modelo B 512 MB de RAM
    • PHP + Apache en Localhost
  3. Pine A64 1GB de RAM
    • PHP + Apache en Localhost
  4. Laptop Core i7 séptima generación 12GB de RAM
    • PHP + Apache en Localhost + túnel SSH inverso + NGINX (proxy inverso) + Subdominio (si, ha sido la configuración mas loca que he hecho)

Claro, cada instalación ha tenido sus diferentes problemas, pero en general, el actualizador es el problema principal. Actualmente la configuración mas efectiva que tengo funcionando es la de VPS 1 GB de RAM con PHP-FPM con NGINX.

Aunque en un principio pensé que podría ser cosa de la RAM, el haber tenido el mismo problema con una computadora de 12GB me hizo entender que tal vez haya algo mas de fondo. Aun así, lo mejor es resolver y seguir y ya encontraré la razón después.

Error: Step 5 is currently in process. Please reload this page later.

Este error aparece al intentar actualizar NextCloud con la interfaz web por primera vez al atascarse en el paso 5 del proceso de actualización.

Error Step 5 is currently in process. Please reload this page later

Luego de fallar en la actualización, el actualizador web deja de funcionar.

Step 5 is currently in process. Please reload this page later

Este error es bastante curioso pues no afecta al funcionamiento de NextCloud. Aun así, lo recomendable es mantenerse al día con las actualizaciones correspondientes

Si buscas en la carpeta de datos de tu instancia de NextCloud, puedes encontrar este registro, indicando que se ha atascado en el paso 5. Puedes borrarlo y el actualizador volverá a comenzar, aunque se atascará en el mismo paso.

Step 5 is currently in process. Please reload this page later

Para corregir el problema, debes hacer lo siguiente:

Elimina el archivo updater.log en el directorio de datos.

En el mismo directorio, elimina la carpeta updater_ocheg, este proceso puede tomar algún tiempo.

A partir de aqui, puedes seguir los pasos de la documentacion oficial para la actualizacion por consola, como esta descrito en este enlace:

https://docs.nextcloud.com/server/latest/admin_manual/maintenance/update.html

sudo -u www-data php --define apc.enable_cli=1 /var/www/nextcloud/updater/updater.phar

Este comando permite ejecutar el actualizador mediante la lineal de comandos y se mostrará el asistente de actualización por línea de comandos. Sigue los pasos indicados.

Asistente de actualizacion de NextCloud por Interfaz de linea de Comandos (CLI)
Asistente de actualizacion de NextCloud por Interfaz de linea de Comandos (CLI)
Actualizacion NextCloud completa

Una vez terminada la actualización, se mostrará un mensaje de éxito y la actualización estará completa.

Conclusiones

Es probable que para cada necesidad haya software que la resuelva, y para cada software pagado haya una version libre. Y aunque libre suela estar asociado a gratis, aunque no haya un costo real, fisico o tangible, es cierto que terminamos pagando de otras formas, por ejemplo, estudiando muchas cosas tecnicas, pagando por VPS, invirtiendo tiempo que podriamos estar gastando en algo mas productivo y cosas asi.

El selfhosting es una iniciativa que me ha encantado. Es divertido experimentar y descubrir soluciones a problemas extraños y, aunque haya mucha, muchisima frustracion, es algo que siempre termino repitiendo. Probablemente haga un post hablando sobre el selfhosting mas adelante, pero por ahora, aqui queda esta entrada.

Anécdota: ¡Out of Memory! ¡Sacrifiquen a los niños!

Esto puede ser una lección sobre como las copias de respaldo pueden salvar vidas.

Introducción

Debido a diversas circunstancias, actualmente tengo dos instancias VPS de idénticas características, cada una destinada a diferentes propósitos que en la practica resultan difusos; una para experimentos, otra para servicios web persistentes(paginas, blogs, herramientas)

CaracterísticaCantidad
CPU1 vCPU compartido
RAM1 GB
Disco Duro25 GB disco duro (no SSD)
Droplet ultra básico en Digital Ocean. Al momento de escribir este post, su precio es de $5 mensuales.

Yo no tengo experiencia administrando servidores de forma profesional, pero a medida que he ido aprendiendo cosas, he caído en cuenta de que es necesario tener tanto una IP publica, como un equipo permanentemente conectado a la red, para lograr algunas cosas así que he ido aprendiendo cosas nuevas vez tras vez. Algunas, a costa de todos mis datos almacenados.

La historia de hoy comienza con un error común, que he subestimado al pensar que no pasaría nada:

Sep 29 12:52:46 archvb systemd[1]: mariadb.service: Main process exited, code=exited, status=1/FAILURE
Sep 29 12:52:46 archvb systemd[1]: mariadb.service: Failed with result 'exit-code'.
Sep 29 12:52:46 archvb systemd[1]: Failed to start MariaDB 10.4.8 database server.

Este error se solucionaba simplemente reiniciando el servicio, pero esta vez, en lugar de encender el servicio, la respuesta fue esta:

Job for mariadb.service failed because the control process exited with error code.
See "systemctl status mariadb.service" and "journalctl -xe" for details.

Este error no me resultaba nuevo puesto que también es un error normal, pero resultaba inesperado debido a que al volver a intentar reiniciar el servidor, salía de nuevo el mismo error. Algo no estaba yendo bien.

Mi falta de experiencia me condujo a hacer lo mismo que incluso un profesional haría; buscar en Google.

Por supuesto, Google me respondió de la misma forma fatalista como lo haría con una persona preocupada por su salud, solo que en mi caso, todas las soluciones pasaban por borrar la base de datos por completo e iniciar de cero. Terror y depresión instantánea.

Dado que esa instancia la utilizo para hacer experimentos, no hay muchos datos que valgan la pena ahí. A lo mucho los Logs del sistema domótico y los del bot para descargar música, que en general solo es información estadística (que ya perdí en otro evento catastrófico anterior). El problema era que también cree una instancia de Nextcloud, especialmente complicada de mantener.

Entre practica y practica le había tomado cariño y agregando plugin, empecé a agregar datos que puedo rescatar sin problema alguno. Lo complicado era volver a configurar todo Nextcloud de cero.

Busque en los logs del sistema tratando de entender lo que sucedía, pero no he habilitado un log de archivo por parte del servicio. Por supuesto, la carpeta estaba vacía.

Echándole un ojo al dmesg, encontré este mensaje:

[11668880.114121] Out of memory: Kill process 25000 (mariadbd) score 96 or sacrifice child
[11668880.118537] Killed process 25000 (mariadbd) total-vm:1085072kB, anon-rss:97060kB, file-rss:0kB, shmem-rss:0kB

Esta información solo confirmaba mis sospechas, pero seguía sin dar con una solución. Decidí entonces apagar todos los servicios para empezar a borrar los archivos como decían las instrucciones. Hasta que se me ocurrió intentar por ultima vez levantar el servicio de mysqld. ¡Y se levantó!

A salvo. ¿Ahora que?

No es la primera vez en la que mi servidor cae. Pero si es la primera en la que lo hace tras optimizarlo y mimarlo tanto, por lo que pensé que lo mejor seria realizar un respaldo regular de las bases de datos para empezar. ¿Pero donde?

  • Es posible crear una tarea programada con CRON para extraer una copia de seguridad en el mismo servidor, pero ¿de que serviría si esta cae junto a el?
  • Para empezar, de todas formas, comenzare haciendo una copia de seguridad local regular.
  • Es posible, mediante SSH, crear una copia de seguridad en un equipo físico, también con CRON. Hacerlo de forma regular permite poder restaurar hasta algún punto en el que no haya arruinado algo al travesear con las configuraciones.
  • Debo investigar si es posible utilizar otros servicios en la nube para crear respaldos. Tal vez Google Drive o OneDrive, puesto que tampoco tengo algo tan valioso como para pagar un servicio en la nube.
  • Y por supuesto, Todos los respaldos deben ser automáticos. Hacerlo de forma manual es probablemente mas inseguro que no hacerlo.

Consideraciones

Por el momento estoy investigando como hacer respaldos regulares, Locales, pero regulares, utilizando el cliente de consola MySQL y el automatizado de procesos CRON. Y realmente me esta gustando CRON. No entiendo por que le tuve tanto miedo si ha sido tan fácil de usar. Aunque tengo que conceder que todo lo que se maneje por consola es aterrador y mas si es poco o nada intuitivo.

Incidente: Colapso de instalación de WordPress al actualizar Jetpack

A veces es fácil olvidar que los programas que utilizamos a diario son escritos por humanos, así que nos suele tomar por sorpresa cuando estos fallan. Se nos suele advertir que hagamos copias de respaldo de nuestros datos, pero el engorroso proceso para restaurar estos datos, hace que descuidemos toda medida preventiva y todo va bien, hasta que empieza a ir mal.

El caso de hoy es uno que me sirve de advertencia sobre las actualizaciones automáticas. En general funcionan bien, pero pueden haber sorpresas. Incluso WordPress nos lo advierte cuando las activamos. En mi caso, he hecho caso de la advertencia, activándola únicamente para la extensión que menos creí posible que falle; Jetpack.

Si, es cierto que las actualizaciones de Jetpack suelen estar cargadas de nuevas funcionalidades, pero no era difícil confiarse. Especialmente cuando se lidia con otros problemas, que se suelen resolver con un solo click.

Usualmente resuelvo los problemas del servidor luego de echarle un ojo a los logs del sistema y reiniciar los servicios, pero esta vez no tenia tiempo asi que solo reinicie los servicios sin revisar los logs y me olvide del asunto. Entonces una hora luego, me aparecio este mensaje:

El sitio estaba una hora caído. Mi pequeño blog no tiene muchas visitas y (por ahora) no esta monetizado, así que no me preocupaba. Volví a reiniciar los servicios y continúe resolviendo otros problemas.

La notificacion de que el sitio continuaba caido me atormentaba hasta que por fin tuve tiempo para dedicarle al servidor y comence el diagnostico.

Primero, revisar que mensaje de error muestra el navegador:

Ha habido un error critico en esta web. Si los servicios estuvieran caídos, el error debería ser de conexión rechazada, así que no era cuestión de los servicios.

Lo segundo a hacer es acceder a la consola del servidor y buscar en los logs algún patrón que parezca sospechoso en el log de errores e /var/log/apache2/error.log y encontré lo siguiente:

PHP Fatal error:  Uncaught Error: Failed opening required '/var/www/interlan.ec/wp-content/plugins/jetpack/jetpack_vendor/automattic/jetpack-waf/src/../rules/allow-ip.php'

Este error no tiene misterios. Lo único que habría que hacer era desactivar el plugin de Jetpack para resolver el problema y reinstalarlo. Un plan perfecto, sin fisuras.

Desafortunadamente era imposible entrar al panel de administración de wordpress.

Mi otro blog pasó por exactamente el mismo problema, pero a diferencia del primero, alcanzó a enviarme un enlace al modo de recuperacion que existe desde la versión 5.1 de WordPress, así que al usarlo, bastó con desinstalarlo y reinstalarlo. Esta es una funcion muy util, si el colapso da el tiempo a mandar el correo con la ulr de rescate.

En el caso de mi blog principal, la URL no fue enviada, asi que tuve que buscar el plugin y borrarlo manualmente, con lo que recuperaría el control y volvería a abrirlo al publico.

Por supuesto, fue necesario algo de limpieza adicional para que todo vuelva a esta en orden. Al menos por un tiempo.

Cuando otra vez colapso con el mismo problema, habia una tercera cosa que quedaba por hacer para entender lo que estaba pasando. Recurrir a google.

Una de las cosas que mas me fastidia del internet actual es que la gente ya solo hace consultas en redes sociales, por lo que buscar en google puede dar resultados muy antiguos, de la epoca en la que los foros publicos estaban mas activos.

Minar preguntas en stack overflow llevaria mucho tiempo, asi que tras borrar jetpack otra vez, abri el instalador de su plugin y me encontre con que habian sacado una nueva version, apenas una hora antes. Era evidente de que ellos estaban consientes del problema que habían causado.

El unico bug que arreglaron era justamente uno sobre mi problema actual:

Firewall: prevent sites from crashing when updating Jetpack versions due to a missing generated file.

Explorar los comentarios fue suficiente para saber que no era el único con el problema y tras deleitarme un poco con las quejas de otros, decidí que lo mejor seria dejar deshabilitado jetpack hasta que salga una nueva actualización que corrija lo que la actualización para corregir un problema, había dañado.

Por supuesto, resolver este problema me ha enseñado algunas lecciones y espero que si alguien tiene el mismo problema, se pase por mi solución antes de intentar resolverlo con medidas desesperadas.

Tutorial: Introducción a la POO mediante ejemplo practico 

Python Screensaver. Debido a que el proceso de actualizar pantalla depende de la velocidad del procesador, en equipos modernos se ve feo.

Escritura de un programa simple de salvapantallas con Python, utilizando programación orientada a objetos.

Tabla de Contenidos:

  • Introducción
  • Sobre el aprendizaje desde los fundamentos
  • Estructura del Tutorial
  • Requerimientos previos del tutorial
  • Desarrollo
    • Objeto «universo.cielo.componentes»
    • Objeto «universo.estrellas.componentes»
    • Raíz del programa: «main.py»
  • Flujo del programa
    • Inicialización de clases
  • Conclusiones
  • Webgrafía

Introducción

Como buen principiante que soy, he tenido que deambular buscando tutoriales que me expliquen como hacer ciertas cosas y que resuelven con relativo éxito mis necesidades. Además, mis comienzos en la programación con la universidad fueron muy anticuados, siendo que estudie programación estructurada en lugar de programación orientada a objetos, lo que me dificulta mucho aprender lo ultimo.

Como seguro se habrán dado cuenta, la mayoría de los tutoriales buscan llegar de un punto «A» a un punto «B» sin complicarse mas, para lo que recurren a una forma simple de programación que puede derivar en lo que se conoce como «código spaguetti», mientras que para aprender programación orientada a objetos, hay que recurrir a textos mecánicos que explican superficialmente y usan ejemplos muy cortos que no aportan demasiado para entender los conceptos.

Este tutorial probablemente no sea el mejor de su tipo, pero mi objetivo es escribir algo que siento que me hubiese servido a entender mejor algunos conceptos, para lo cual he recurrido a un programa escrito en Python por Suraj Singh quien ha escrito un tutorial breve, pero ha aportado un código super comentado que he traducido, comentado mas y reestructurado para poder explicar algunos conceptos que considero son necesarios para entender mejor sobre Python y programación orientada a objetos.

Quiero aclarar que el tutorial de Suraj Singh no es malo, de hecho, lo considero genial y es la razón por la que lo estoy usando de ejemplo, sino que quiero expandir lo que enseña para cumplir con mis expectativas sobre el aprendizaje desde los fundamentos.

Sobre el aprendizaje desde los fundamentos

Cuando comenzamos a programar, es muy probable que hayamos empezado por necesidad, por lo cual seguramente habremos buscado con desesperación códigos y los hemos apilado hasta que funcionaran. Esto nos permite aprender muchas cosas y muy rápido, con lo que podemos concluir con éxito lo que nos movió a buscar aprender a programar en primer lugar, pero nos hace confiarnos y descuidar los fundamentos que mas adelante nos permitirían resolver tareas con mas eficiencia y eficacia.

Estructura del Tutorial

Este tutorial tratará de explicar de forma detallada todos los conceptos requeridos para lograr la creación de un salvapantalla simple escrito en Python. Hay que recordar que no es un salvapantalla real, en el sentido de que no puede ser instalado en las plataformas nativas de salvapantallas de Windows o Linux y solo es un modelo básico para explicar los conceptos.

Estará agrupado en dos partes:

  • Tutorial documentado con código y gráficos.
  • Código comentado, listo para ejecutar.

Requerimientos previos del tutorial

  • Saber escribir un hola mundo en Python

Si, solo eso. Hay muchas cosas implicadas en escribir hola mundo en Python, por ejemplo, tener instalado el compilador, saber usar la consola y muchas cosas mas.

Al tener instalado el compilador también se incluyen muchas herramientas y en la mayoría de instalaciones al menos, ya vienen incorporadas las herramientas de Tkinter, para la creación de ventanas en modo gráfico.

Aunque el tutorial puede ser ejecutado en el «Notepad» si les parece bien, yo recomiendo el uso de «Visual Studio Code», junto a los complementos de Python, porque tiene funciones de asistencia que facilita depurar e incluso aprender como funcionan las cosas. Si quieres, puedes presionar la tecla Control + Click izquierdo para poder ver la función, clase o variable donde fueron definidas.

Desarrollo

La programación orientada a objetos, busca la optimización del código mediante el agrupamiento de funciones y atributos conocidos como «objetos». Para entenderlo provisionalmente, he decidido tratar de visualizar, tanto la estructura de carpetas como la organización de funciones, clases y atributos, como objetos del mundo real, por lo que la estructura del directorio representa una caja y la he etiquetado como «python screensaver»

Dentro de esta caja he creado el primer archivo llamado «main.py» que contendrá la lógica del programa y dentro de la misma, he agregado otra caja que he llamado «universo».

Python permite trabajar con módulos, que no son mas que archivos que contienen código adicional que mantenemos separado del archivo principal para que sea mas fácil de editar y depurar. Durante la ejecución, el interprete unirá todos los archivos en un solo script y los leerá secuencialmente para poder ejecutar el programa en un proceso detallado en el modelo de ejecución, en la documentación oficial.

Para crear un módulo, debemos crear una carpeta que contenga un archivo vacío con nombre «__init__.py». una vez creado, no le volveremos a tocar.

Dado que el programa que estamos escribiendo es un salvapantalla de un cielo estrellado, los nombres de los módulos deberían reflejar esto, por lo que la carpeta universo contendrá al cielo, con sus funciones(o sea, el código que define su comportamiento) y atributos y a las estrellas, también con sus funciones y atributos. (Podríamos ignorar un poco el debate de si el cielo contiene a las estrellas o el universo contiene al cielo). Esto deja nuestro directorio de la siguiente manera:

Jerarquía archivos Python screensaver

Es recomendable que cada parte del código sea fácil de entender, que cada nombre y variable sea fácil de deducir y todo este muy bien ordenado porque, aunque al inicio funcione, si hay que modificar o corregir mas adelante, puede llegar a ser muy complicado y enigmático. Es por eso que en esta ocasión he elegido este esquema para los nombres de carpetas y módulos. Por supuesto, si se desea se puede usar cualquier esquema, no hay reglas estrictas en este sentido.

Nota: Usualmente el primer archivo de tu código debería llamarse «main.py», puesto que este es el nombre mas frecuentemente usado para indicar que todo el programa comienza por allí, pero en otros lenguajes de programación, como JavaScript(con NodeJS), se llaman «index.js». Esto no es estrictamente necesario puesto que el primer archivo puede tomar cualquier nombre. Solo es un acuerdo tácito entre programadores para facilitar la lectura del código.

En el archivo «main.py» vamos a comenzar haciendo nuestras primeras importaciones de módulos del sistema:

import tkinter as tk
import time
import random

Los módulos se pueden importar por su nombre, pero durante la escritura del código, tal vez prefieras utilizar un «alias» o apodo, para reducir la cantidad de texto al escribir. En mi caso he importado el módulo tkinter como tk, pero en otros tutoriales pueden optar por otros nombres. Por supuesto, también puedes importarlo sin ningún alias, no hay problema con eso y como ves, la mayor parte de los módulos aquí usados aparte de tkinter, se usan de esa manera.

Nuestro salvapantalla tiene que preparar algunas cosas antes de visualizarse, por lo que nuestro siguiente componente en ser cargado es la clase «cielo» dentro del módulo «universo».

Cuando creamos nuestra estructura de archivos, consideramos la carpeta «Python screensaver» como nuestro contenedor y dentro del mismo, creamos la carpeta «universo».

Al definir el archivo «__init__.py», la carpeta pasa a ser parte del módulo, por lo que al importar el objeto «cielo», la buscará en el módulo universo, según este esquema:

import nombre_módulo.nombre_objeto

Así que importaríamos nuestro módulo de la siguiente manera:

import universo.cielo

Nuestro trabajo ahora consiste en desarrollar el objeto «cielo.componentes».

En el módulo cielo debemos repetir la importación de los módulos, pero en este caso solo hace falta uno:

import tkinter as tk

Además, importaremos también el otro objeto creado en el módulo universo llamado estrellas.

import universo.estrellas

Objeto «universo.cielo.componentes»

Según la Wikipedia:

Un objeto es un ente orientado a objetos (programa de computadoras) que consta de un estado y de un comportamiento, que a su vez constan respectivamente de datos almacenados y de tareas realizables durante el tiempo de ejecución. Un objeto puede ser creado instanciando una clase, como ocurre en la programación orientada a objetos, o mediante escritura directa de código y la replicación de otros objetos, como ocurre en la programación basada en prototipos.

En nuestro caso, los atributos de nuestro módulo «cielo» se encuentran en nuestra clase «componentes» donde está definida una función que establece los parámetros iniciales de funcionamiento y variables que contienen sus atributos (def __init__(self, *args, **kwargs)) y dos funciones que definen el comportamiento del cielo (def crear_estrellas(self) y def actualizar_pantalla(self))

El código de la clase «universo.cielo.componentes» queda entonces de la siguiente manera:

import tkinter as tk
import universo.estrellas

CANTIDAD_ESTRELLAS = 2

class componentes(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) 
        self.estrellas = []
        self.crear_estrellas()

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

Objeto «universo.estrellas.componentes»

Nota: No hay una forma lineal de escribir código en cuanto a programación orientada a objetos se refiere. Como te has dado cuenta, hasta el momento hemos creado una clase y ahora crearemos otra clase, ninguna de las cuales puede ser ejecutadas individualmente. Nuestro archivo «main.py» continua vacío excepto algunas importaciones que agregamos al principio. Esto es debido a que el código ya ha pasado por algunas pruebas y esquematizaciones, lo que permite crear este código ya organizado.

Es cuestión del programador el determinar la mejor forma de escribir su código, pero para practicar te recomiendo escribir programas sencillos en un solo archivo y practicar luego modularizándolos y convirtiendo en objetos algunas funciones y variables. Lo importante es entender los conceptos antes de tirarse a un proyecto mas grande, aunque si han llegado a este tutorial, es probable que ya hayan puesto en marcha algo de código antes de entender como funciona.

Al igual que en el módulo anterior, hacemos las respectivas importaciones, creamos las variables globales y creamos la clase:

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"

class componentes:
    def __init__(self, padre):
        self.padre = padre
        self.comenzar_movimiento()
        self.crear_circulo_pequeño()
    
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Raíz del programa: «main.py»

Podría parecer un poco extraño no haber comenzado desde el inicio, pero el objetivo de este tutorial no es explicar el diseño de un código modular, sino explicar los conceptos implicados en el desarrollo del código mediante programación orientada a objetos. Debido a esto, recién llegamos a la parte que unificará todo el código escrito hasta el momento.

El código entonces, quedaría así:

import tkinter as tk
import time
import random
import universo.cielo

COLOR_FONDO="black"
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>']

def main():
    raiz=tk.Tk()#establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)
    pantalla.pack(expand="yes",fill="both") 
    
    raiz.wait_visibility(pantalla)
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA)
    raiz.wm_attributes("-topmost", True)
    raiz.overrideredirect(1)
    raiz.state('zoomed')

    def salir(event):
        raiz.destroy()
        return
    
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update()
        raiz.update_idletasks()
        pantalla.actualizar_pantalla()

if __name__ == '__main__':
main()

Flujo del programa

El código ya se encuentra completamente comentado y explicando que hace cada función, variable, clase y modulo, pero hare unas cuantas aclaraciones aquí.

Inicialización de clases

Importar los módulos no es suficiente para empezar a usarlos. Es necesario inicializarlos, para lo cual, se los llama solos o dentro de una variable, pasándoles los parámetros necesarios para funcionar.

El modulo «universo.cielo.componentes» es inicializada en la función «main()» del archivo main.py de la siguiente manera:

pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO)

La clase «universo.cielo.componentes» recibe como parámetro, la inicialización que hemos realizado del componente «tkinter». Esto nos evita tener que inicializar las clases y redefinir nuestros parámetros cada vez que lo necesitemos. Reutilizar código es una de las piezas clave de la Programación Orientada a Objetos y en este caso, lo que haremos se conoce como «Herencia»

La clase «universo.cielo.componentes» recibe los parámetros e inicializa las variables y funciones necesarias. Es decir, crea una instancia de la ventana que hemos creado, crea un arreglo que contendrá las estrellas e inicializara la función «crear_estrellas()» para definir el comportamiento inicial de la clase.

def __init__(self, *args, **kwargs):
    tk.Canvas.__init__(self, *args, **kwargs) 
    self.estrellas = []
    self.crear_estrellas()

En Python, se utiliza la palabra «self» para representar una instancia de la clase. Esto le permite acceder a los atributos y funciones que esta posee. Todas las funciones definidas en la clase, deberán utilizar esta palabra para poder acceder a sus variables inicializadas en «__init__»

El arreglo «self.estrellas» no contendrá datos, sino instancias de la siguiente clase que vamos a inicializar: «universo.estrellas.componentes»

La función «crear_estrellas()» crea un ciclo donde se agregan diferentes instancias de la clase «universo.estrellas.componentes» de acuerdo a la cantidad que hayamos definido en la variable «CANTIDAD_ESTRELLAS»

def crear_estrellas(self):
    for i in range(CANTIDAD_ESTRELLAS):
        self.estrellas.append(universo.estrellas.componentes(self))
    return

Las clases son instanciadas cada vez que son invocadas. En el caso de «universo.estrellas.componentes», sera instanciada 2 veces según lo definido en «CANTIDAD_ESTRELLAS», lo cual significa que el objeto estrella llega a existir dos veces, lo cual es equivalente a tener físicamente dos estrellas, con sus respectivos atributos y comportamiento.

La clase «universo.estrellas.componentes», al momento de ser inicializada, heredará todos los atributos de la clase que la llama, estableciéndose una relación «Padre-Hija» Esto se logra enviando como parámetro la palabra «self» y recibiéndola en una variable que en este caso llamaremos «self.padre»

def __init__(self, padre):
   self.padre = padre
   self.comenzar_movimiento()
   self.crear_circulo_pequeño()

La clase «universo.estrellas.componentes» al ser inicializada, guarda la información de la clase padre en la variable «self.padre» y llama dos funciones: «self.comenzar_movimiento()» y «self.crear_circulo_pequeño()». Estas funciones establecen los parámetros iniciales del objeto en la pantalla y crean el objeto en la pantalla.

Una vez inicializadas las clases, la función «main()» tiene acceso completo a sus atributos y funciones, por lo cual, se procede a actualizarlas desde el loop«While True»

while True:
    raiz.update() 
    raiz.update_idletasks()
    pantalla.actualizar_pantalla()

La variable «pantalla» ahora tiene acceso a la función «actualizar_pantalla()» dentro de la clase «universo.cielo.componentes» la cual hará un barrido de los objetos creados en «self.estrellas» para que actualicen sus propiedades.

«universo.cielo.componentes.actualizar_pantalla()»

def actualizar_pantalla(self):
    for i in self.estrellas:
        i.actualizar()
    return

«universo.estrellas.componentes.actualizar()»

def actualizar(self):
    self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
    x,y = self.padre.coords(self.estrella)[:2]
    if x<0 or x>1500:
        self.parar_movimiento()
    elif y<0 or y>1000:
        self.parar_movimiento()
    return

Conclusiones

La programación orientada a objetos puede ser muy confusa a veces. Como hemos visto, un programa relativamente sencillo puede ser un laberinto difícil de rastrear, sin embargo, nos permite realizar cosas complejas de forma mas eficiente.

Entender la programación orientada a objetos desde su enfoque inicial, como una representación de los objetos de la vida real, puede facilitar mucho las cosas. Es de sentido común que todo objeto que nos rodea, tiene atributos y comportamientos definidos, pero es un poco mas abstracto los conceptos de herencia, abstracción, polimorfismo, acoplamiento y encapsulamiento, pero es un buen punto de partida.

Conforme se siga practicando, es posible entender cosas mas complejas y también es posible llegar a entender, por que se hizo tan popular en la actualidad.

Webgrafía

Módulos en Python. Por Learn Python. https://www.learnpython.org/es/Modules%20and%20Packages

how to create beautiful moving stars/snow like screen saver using python and tkinter – tutorial – two | python gui example. Por Suraj Singh. https://www.bitforestinfo.com/blog/04/11/how-to-create-beautiful-movingstars-r-snow-like-screensaver-using-python-and-tkinter.html

Objeto(programación) https://es.wikipedia.org/wiki/Objeto_(programación)

Código Espagueti https://es.wikipedia.org/wiki/Código_espagueti

Modelo de Ejecución https://docs.python.org/es/3/reference/executionmodel.html

Herencia (informática)https://es.wikipedia.org/wiki/Herencia_(inform%C3%A1tica)

Código y esquema de directorio:

Jerarquía archivos python screensaver
Jerarquía archivos python screensaver
# Importar los componentes necesarios
import tkinter as tk
import time
import random

#importar los componentes que contienen las clases que hemos creado
import universo.cielo

#Definir variables globales
COLOR_FONDO="black" #Establecer la variable que define el atributo del color del cielo
NIVEL_TRANSPARENCIA=1
EVENTOS_DE_CIERRE = ['<Any-KeyPress>', '<Any-Button>'] #Se puede crear un arreglo de eventos para inicializarlos todos de una vez


def main():
    raiz=tk.Tk()#Establecer la ventana raiz o inicial

    pantalla=universo.cielo.componentes(raiz,bg=COLOR_FONDO) #crear la ventana, utilizando la clase «cielo» como lienzo donde se dibujaran los componentes
    pantalla.pack(expand="yes",fill="both") #agrega los componentes en el lienzo
    
    raiz.wait_visibility(pantalla) #Se espera a que la ventana sea construida para cargar los componentes
    raiz.wm_attributes('-alpha',NIVEL_TRANSPARENCIA) #Se agrega el atributo de transparencia a la ventana
    raiz.wm_attributes("-topmost", True) #Se agrega el atributo «por encima de todo» a la ventana
    raiz.overrideredirect(1) #Evita redimensionar la ventana
    raiz.state('zoomed') #Inicia la ventana en estado "maximizado"

    #Esta funcion permite terminar el programa al ser disparada por los eventos definidos en la variable EVENTOS_DE_CIERRE
    def salir(event):
        raiz.destroy()
        return
    
    #Permite vincular la ventana a la lista de eventos definidos en la variable «EVENTOS_DE_CIERRE»
    for seq in EVENTOS_DE_CIERRE:
        raiz.bind_all(seq, salir)

    while True:
        raiz.update() #Actualiza la ventana
        raiz.update_idletasks() #Actualiza la ventana si no hay actividad
        pantalla.actualizar_pantalla() #Recarga la ventana mediante la clase cielo

#establecemos la funcion main como arranque al iniciar el script
if __name__ == '__main__':
 main()
#Importar los componentes necesarios
import tkinter as tk

#Importar los componentes que contienen las clases que hemos creado
import universo.estrellas

#Crear una variable globlal para la cantidad de estrellas en pantalla.
CANTIDAD_ESTRELLAS = 2

# Definir la clase componentes
class componentes(tk.Canvas):
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs) # Crear un objeto tkinter con los parametros iniciales
        self.estrellas = [] # Establecer un arreglo de «estrellas» obtenidas de la clase «universo.estrellas.componentes»
        self.crear_estrellas() # Llamar a funcion crear_estrellas() al inicializar la clase

    # Funcion para crear estrellas
    def crear_estrellas(self):
        for i in range(CANTIDAD_ESTRELLAS): # Crea un arreglo de instancias de universo.estrellas.componentes de acuerdo a la cantidad definida en la variable CANTIDAD_ESTRELLAS
            self.estrellas.append(universo.estrellas.componentes(self))
        return

    #Funcion para actualizar la posicion de las estrellas en el cielo
    def actualizar_pantalla(self):
        for i in self.estrellas:
            i.actualizar()
        return

import random

VELOCIDAD_RADIO=[i/10.0 for i in range(-10,-2)]+[i/10.0 for i in range(2,10)]
RADIO=25
COLOR_ESTRELLA = "yellow"



class componentes:
    # Definir la clase inicial, que recibira los parametros desde donde sea inicializada.
    def __init__(self, padre):
       self.padre = padre
       self.comenzar_movimiento()
       self.crear_circulo_pequeño()
    
    # Establece los parametros iniciales para realizar los movimientos del objeto en la pantalla
    def comenzar_movimiento(self):
        self.x1 = self.padre.winfo_width()/2
        self.y1 = self.padre.winfo_height()/2
        self.velocity_x = random.choice(VELOCIDAD_RADIO)
        self.velocity_y = random.choice(VELOCIDAD_RADIO)
        return

    # Crea un circulo en la pantalla
    def crear_circulo_pequeño(self):
        x1=self.x1
        y1=self.y1
        x2,y2=x1+RADIO, y1+RADIO
        self.estrella = self.padre.create_oval(x1,y1,x2,y2, fill=COLOR_ESTRELLA)
        return

    # Detiene el movimiento del objeto en pantalla
    def parar_movimiento(self):
        self.padre.coords(self.estrella, self.x1,self.y1,self.x1+RADIO,self.y1+RADIO)
        self.comenzar_movimiento()
        return

    # Actualiza la posicion del objeto en pantalla
    def actualizar(self):
        self.padre.move(self.estrella, self.velocity_x, self.velocity_y)
        x,y = self.padre.coords(self.estrella)[:2]
        if x<0 or x>1500:
            self.parar_movimiento()
        elif y<0 or y>1000:
            self.parar_movimiento()
        return

Actualización:

¡Codigo en GitHub!

Sistema Super Simple de Blogging (3SB)

Super Simple Blogging System aka 3sb
Interfaz de Usuario para 3sb

Autor: Drk0027

Versión: 1.0

ChangeLog: Changelog

Proyecto Desplegado: 3sb.interlan.ec 3SB en GitHub Pages

Documentación: Documentación

GitHub: 3SB en GitHub

Tabla de contenidos

Motivación

La motivación de este proyecto, simplemente es crear una herramienta practica, con la cual pueda entrenar mas mis conocimientos de JavaScript y PWA, además de tener un sistema de blogging muy simple que dependa muy poco del servidor.

Características

Este blog permite toda la gama de CRUD (Crear, Actualizar, Borrar) para cada elemento de la base de datos (entradas.json), pero los cambios se almacenan de forma local en el LocalStorage, por lo que si se quieren hacer públicos los cambios, es necesario exportar el nuevo archivo entradas.json y reemplazar al anterior.

Las características iniciales de este proyecto son las siguientes

  • CRUD completo Es decir, crear, actualizar y eliminar registros.
  • Personalización Se pueden usar plantillas Bootstrap, pero también, al ser tan simple, siempre y cuando se mantengan las etiquetas originales con sus id, puede ser modificado completamente a gusto
  • PWA Al disponer de Service Workers y ser servido mediante https, los datos pueden permanecer en el cliente fuera de lineal, pudiendo ser consultados libremente.
  • Responsive Las características del cliente definen la calidad de la lectura al ajustarse a los parámetros necesarios.
  • Notificaciones push Para poder notificar al cliente si hay una versión mas reciente del archivo entradas.json o indicar que el archivo local ha sido modificado y debe guardarse para mostrar los cambios de forma global.(tal vez próxima versión)
  • Edición con guardado automático Al editar una entrada, los cambios se almacenan automáticamente en el LocalStorage

Sistema de Blogging tradicional

El objetivo de este proyecto no es reescribir la rueda, sino ofrecer una alternativa a los modelos tradicionales de forma didáctica, para que quienes deseen, puedan aprender directamente del código.

Para esto, se mantienen aspectos tradicionales de los blogs como:

  • Presentación de entradas en forma descendente Muestra las entradas mas recientes primero en la pantalla inicial
  • Búsqueda simple por contenido de las entradas La búsqueda es muy simple, se selecciona un array de palabras ingresadas en el campo de búsqueda y se confirma si estas existen en las entradas. Se creara una lista de entradas que contengan los criterios de búsqueda (por implementar)
  • Búsqueda por etiquetas Se mostraran solo las entradas que tengan las etiquetas seleccionadas
  • ¿Comentarios? Dado que solo se cargan paginas estáticas, no es posible almacenar comentarios. En todo caso, tal vez sea posible vincular las url con sistemas como Discus y otros.
  • Gestión SEO No es precisamente imposible, pero la mayor parte del procesamiento se realiza desde el lado del cliente, por lo que, si bien, los motores de búsqueda pueden encontrar por ejemplo, el archivo index.html, no podrían leer los contenidos de las entradas, ya que esto requiere JavaScript y no solo texto plano. He leído que hay algunos buscadores que ya crean una representación virtual del tiempo de ejecución, por lo que pueden localizar información que solo se carga durante la ejecución en el cliente, pero no he investigado mas.

Limites del sistema

Realmente no se que limites podría tener, pero estimo que esta bastante limitado, debido a que carga un archivo json que crecerá bastante con el tiempo.

Al parecer cada navegador tiene sus propias restricciones de memoria, pero básicamente la mayoría tienen espacio ilimitado para el LocalStorage, el problema esta mas relacionado con la memoria RAM.

Si bien el almacenamiento no es mayor problema, el archivo Json es cargado directamente en la memoria RAM, así que su consumo es directamente proporcional al tamaño neto del archivo.

Los procesos del sistema no consumen demasiada memoria, pero al renderizar tienen que cargar el archivo por completo para procesarlo, es posible que comiencen a sentirse los efectos pasado los 10MB, puesto que el consumo de memoria suele incrementarse de forma exponencial al tamaño del archivo. Es bastante impresionante lo que puede cargar una base de datos normal. Mis respetos.

Solo por pura diversión, intentare hacer pruebas en un dispositivo kaiOS, Después de todo, este proyecto comenzó pensando en un Alcatel 3078A

Sobre las imágenes y archivos

Se pueden utilizar las mismas etiquetas para incrustar imágenes en el documento, siempre y cuando se hayan subido primero las imágenes al directorio destinado imágenes o en su defecto, donde se desee almacenar los recursos requeridos

Instalación

Basta con copiar los archivos en la carpeta publica del servidor. Tal vez cuando lo suba a GitHub también se pueda usar el comando git clone para descargar el código y ejecutarlo

Webgrafía

How to Build a Lightweight Blog

Is It Possible to Build a Blog With HTML Only?

Deploy a JavaScript only Blog With CMS.JS

CMS.JS Proyecto muerto

Acceder a un terminal mediante SSH inverso

Una de las cosas mas útiles que he deseado tener disponible, es el acceso SSH a mi computadora persona, desde cualquier parte del mundo. He conocido herramientas como ngrok y localtunnel que sirven, mas o menos, para lo que necesito, pero quería intentar con una herramienta que no dependa de servidores externos y ajenos a mi poder, sea porque no quiero compartir mis datos con terceros, o porque simplemente quería aprender a hacerlo por mi mismo.

Luego de mucho investigar, he encontrado algo que existe desde hace mucho tiempo y que probablemente es conocido por todo el mundo, pero recién aprendí a hacerlo por mi cuenta; SSH permite hacer túneles inversos.

Esquemas de control remoto

Desde el periodo de confinación por el COVID-19, todo tipo de servicios se hicieron sumamente populares, como zoom para las conferencias, y AnyDesk para trabajo remoto.

Aunque es de conocimiento común, se puede controlar un equipo utilizando el modelo Cliente-Servidor, donde el cliente se conecta al servidor y el servidor responde a sus peticiones.

Modelo Cliente-Servidor
Modelo Cliente-Servidor

En este modelo, el equipo a controlar debe ser el servidor, es decir, debe mantenerse escuchando para poder responder las peticiones del cliente, es decir, el controlador.

Este modelo es sencillo si hay conexión directa entre el cliente y el servidor, por lo que se puede utilizar la siguiente combinación de programas para el acceso remoto:

  • SSH – SSHd Acceso mediante consola (y montones de trucos mas).
  • RDP/XRDP – Escritorio remoto de Windows/Remina(Linux) Acceso mediante escritorio remoto usando el protocolo RDP de Windows.
  • Vino-Vinagre Acceso mediante VNC que admite múltiples sistemas operativos.

El problema entonces es que todo esto funciona en una red local, pero no a través de internet.

La gran nube

La red de redes
La red de redes

Podríamos definir a internet como la red que une a muchas redes pequeñas mediante un mapa de rutas complejas trazado por algoritmos aun mas complejos de entender.

Inicialmente, los proveedores de internet buscan conectar un solo dispositivo a internet, pero en una casa, esto no es lo ideal. Hay muchos dispositivos y cada día parecen haber mas, por lo que una sola conexión no abastecería.

Para solucionar esto, se crea una pequeña red local mediante routers que guiaran todas las conexiones hacia el exterior.

Es probable que por seguridad el proveedor de internet haya limitado las conexiones del router a solo salientes, por lo que no hay forma de conectarse desde afuera.

También es probable que el mismo proveedor haya creado una infraestructura compleja de subredes anidadas que dificultan aun mas encontrar el camino hacia un equipo en especifico desde el exterior. Casi cualquier dispositivo que no esté en la red local, estará en la misma situación, por lo que la comunicación entre ambos es virtualmente imposible.

La Solución

Internet es una maraña de dispositivos conectados, pero si alejamos lo suficiente nuestra perspectiva, nos daremos cuenta de que en realidad, parece una gigantesca red local.

Esto significa que es posible conectarse a algún nodo que tenga conexión directa y publica a internet, utilizando lo que es llamado IP Pública

Algunos proveedores de internet ofrecen el servicio de IP publica a sus clientes, por lo que ajustando algunas configuraciones en el router, ya se puede acceder desde cualquier parte del mundo a nuestro equipo, pero esto es innecesariamente peligroso, por cuanto internet es un lugar plagado de atacantes buscando vulnerabilidades en cada IP que puedan encontrar.

Para resolver esto y evitarle a los clientes todo tipo de pesadillas, las empresas proveedoras de servicios de control remoto, crearon servidores de conexión inversa, que sigue un modelo de Cliente – Servidor – Cliente.

Modelo Cliente Servidor Cliente
Modelo Cliente Servidor Cliente

Para este ejercicio, vamos a imitar el modelo que utilizan las grandes empresas y crearemos un puente para alcanzar algún equipo de nuestra red local, desde cualquier parte de internet.

Desarrollo de la Solución

Aunque técnicamente se puede realizar con solo dos dispositivos, lo mas común es que se necesiten tres, así que enumeraremos los requisitos a continuación:

  • Dispositivo de destino: Se encuentra aislado en una red local. y es incapaz de recibir conexiones entrantes debido a la configuración de su red y su proveedor de internet, pero puede ejecutar conexiones salientes a cualquier parte de internet.
    • Servidor SSH Puede ser cualquiera, como OpenSSH o Dropbear. Nota importante: el dispositivo debe tener instalado algún servidor SSHd. He hecho pruebas con termux en Android y, aunque tiene uno, su problema esta relacionado con que no permite el login de usuario y contraseña, para lo cual se deben usar otros métodos de autenticación.
    • Cliente SSH Puede ser cualquiera.
  • Servidor: Se encuentra en un lugar accesible de internet y permite conexiones entrantes o salientes libremente. Generalmente es un servidor, pero puede ser cualquier dispositivo mientras tenga una IP pública.
  • Dispositivo de salida: Generalmente está en la misma situación que el dispositivo de destino, pero en otra red. lo importante es que tenga conexión saliente hacia el servidor.

Seguramente ya dispones de los dispositivos de destino y de salida, pero el servidor es algo menos probable, así que, por que no arriesgarse con un droplet de Digital Ocean?

DigitalOcean Referral Badge

Utilizando este enlace puedes crear una cuenta con un crédito inicial de 100 dólares, totalmente sin costo durante 60 días. Úsalo para aprender y practicar con servidores Linux y cuando se acabe el periodo de prueba, puedes comprar Droplets desde 5 dólares mensuales. Recuerda administrar responsablemente tus VPS para evitar sorpresas al final del mes.

Ahora vamos a empezar a aplicar la configuración de los distintos dispositivos implicados:

Dispositivo de Destino

Este dispositivo, que se encuentra atrapado en una red que no permite conexiones entrantes, va a permanecer conectado todo el tiempo al servidor para que el túnel se mantenga vivo. Eso significa que la ventana del terminal tendrá que mantenerse abierta en todo momento o de lo contrario el túnel morirá.

En las aplicaciones de control remoto esto también sucede, pero es mas transparente al usuario debido a que utilizan servicios para mantener vivo el cliente y revivir el túnel cada que sea necesario. En nuestro caso se puede realizar algo similar, enviando la conexión al fondo mediante screen u otras herramientas.

En el dispositivo de destino, hay que escribir lo siguiente

ssh -R 16789:localhost:22 usuario@servidor

donde:

  • ssh Inicia la aplicación de shell segura.
  • -R 16789:localhost:22 vincula un puerto arbitrario(de nuestra elección) en el dispositivo de destino, al puerto 22 del servidor SSHd.
    • 16789 es un puerto arbitrario, es decir, podemos poner cualquiera, siempre que sea un puerto libre en nuestro equipo.
    • localhost o 127.0.0.1 es la dirección local de nuestro dispositivo.
    • 22 es el numero del puerto al que vamos a redirigir la conexión entrante. En este caso, es el puerto de SSHd.
  • usuario@servidor
    • usuario Es el nombre del usuario existente en el servidor al que nos vamos a conectar y utilizar de puente.
    • servidor Es el nombre de dominio o dirección IP del servidor al que nos estamos conectando.

Al ejecutar este comando, se abrirá una conexión con el servidor, que deberás mantener viva tanto como quieras acceder por el túnel inverso. Por supuesto, mientras estas en ese túnel, puedes usar libremente al servidor como si fuera una conexión normal.

Dispositivo de salida

Puede parecer raro comenzar por el dispositivo de salida, pero asumiremos que en el servidor ya está SSHd bien configurado.

En el dispositivo de salida deberás acceder al servidor con una conexión normal de SSH

ssh usuario@servidor

Donde:

  • ssh Es el comando de la consola segura
  • usuario@servidor
    • usuario Es el usuario del servidor
    • servidor Es el nombre de dominio o la dirección IP del servidor

Una vez que haya una sesión iniciada en el servidor, debemos ejecutar este comando y tendremos acceso a la consola del dispositivo de destino.

ssh usuario@localhost -p 16789

Donde:

  • ssh Es el comando de la consola segura.
  • usuario@localhost Es el usuario del dispositivo de destino.
    • usuario es el usuario del dispositivo de destino.
  • -p 16789 Es el puerto que abrimos en el dispositivo de destino.

Si queremos acceder a nuestro dispositivo de destino, hemos de dejar encendido el túnel, pero una falla de la red podría causar que la conexión se cierre. Al no estar cerca del equipo para restaurar la conexión, no podremos revivir el túnel hasta que volvamos presencialmente a restaurarla, así que lo recomendable es utilizar una herramienta que reviva el túnel cada que sea necesario o utilizar un cliente que pueda mantener conexiones persistentes a pesar de los quiebres de red.

Mientras tanto, se pueden utilizar sin problema los demás protocolos que funcionan sobre SSH, como SFTP o SCP por ejemplo.

Eso si, hay que recordar que para conectarse, hay que usar el puerto arbitrario que hemos definido anteriormente.

SFTP:

 sftp -oPort=16789 usuario@localhost

SCP

scp -p -P 16789 /tmp/ssh.txt usuario@localhost:

Hay muchas dudas que me quedan pendientes pero las iré estudiando conforme vaya necesitando mas cosas, pero tener este túnel es algo interesante para practicar.

Bibliografía/Webgrafía

Conexión a servidor mediante túnel inverso ssh Obtenido de https://openwebinars.net/blog/conexion-servidor-mediante-tunel-inverso-ssh/

Túnel inverso. Obtenido de :https://mundo-hackers.weebly.com/tuacutenel-inverso-ssh.html

Creación de túnel inverso. Obtenido de https://campusvirtual.ull.es/ocw/pluginfile.php/2172/mod_resource/content/0/perlexamples/node48.html

Bitácora de desarrollo: Bot Descargar Musica 31/5/22

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Se puede acceder usando el siguiente URL: Descargar_Musica

He comenzado esta especie de portafolio con un pequeño bot de Telegram para descargar música desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Los cambios que he realizado recientemente son muy escasos. Mi conocimiento sobre javascript es relativamente superficial, por lo que he decidido aprender clases, módulos y librerías en javascript. Debido a esto, ahora se han realizado los siguientes cambios:

Creación del directorio data/descargar_musica/descargas

Con el fin de crear un sistema de control para bots unificado, he creado este directorio para que en el futuro, pueda agregar mas directorios de datos para cada bot. En esta situación en especifico, este cambio se hace por la necesidad de asegurar que si hay datos corruptos o incompletos, o un sistema de borrado deficiente, al modificar o borrar los archivos, esto no afecte a otros bots o al propio código fuente.

Creación de las librerias db_logging_system y telegram_logging_system

Con el fin de crear un código mas simple y fácil de mantener, he creado estas librerías universales, que permiten registrar a los nuevos usuarios que utilicen el bot y otra que registre los eventos en un canal distinto.

PD: Sigo sin entender mucho esto, pero ahi logre hacerlo funcionar.

Creación de un vídeo tutorial

Este bot no tiene mayor complicación. Lo único que requiere es que envíen un enlace y comienza a trabajar, sin embargo, siguen mandandole enlaces de spotify e incluso itunes. Si bajar música de estas plataformas fuera legal (y hubiese alguna ganancia aparte de practicar programación) seguro que implementaría una función adecuada, pero por el momento, esto no es asi.

Telegram tiene muchas funciones interesantes y una de mis preferidas son los bots inline. Es posible conseguir la url de un vídeo de youtube, usando el bot @vid y una vez seleccionado el vídeo, mi bot comenzará a trabajar.

Uso de los directorios data/descargar_musica/descargas y data/descargar_musica/info

En la revisión anterior, los vídeos se descargaban en el mismo directorio de ejecución. Sean estos archivos temporales o archivos completos, todos se guardaban sin reparo allí, por lo que si ocurría un error o el sistema caía abruptamente, este directorio que contiene los ejecutables, se llenaba de basura que era difícil de limpiar. Para esto, se ha creado el directorio descargas, dentro de uno asignado al bot en cuestión, donde irán todas las descargas realizadas y las que hayan fallado también.

el directorio info, existe en cambio para guardar datos permanentes que no son parte del código, como el vídeo instructivo mencionado previamente

Otros cambios

  • Se agregaron mas registros para mas eventos (errores, eventos, arranque y comandos)
  • Se limpió el código aplicando las nuevas librerías creadas
  • Se optimizo el tiempo de respuesta del bot
  • Se agregaron nuevos mensajes de respuesta
  • Se agregaron nuevas instrucciones para situaciones no controladas
    • En caso de introducir un enlace no reconocido, se agregó la misma instrucción del comando sobre_mi.
    • En caso de haber un error al descargar, se agregó un mensaje de notificación.
    • En caso de haber un error al convertir el archivo.
    • En caso de haber un error al eliminar los archivos temporales.

Proximos cambios

El bot ya se encuentra en un estado bastante aceptable para lo que queria aprender, pero deseo aprender mas cosas, por lo que espero poder agregar lo siguiente:

  • Registro particular para cada bot (sistema “Panoptic”)
  • Control de depuración por comandos para el bot
  • Control web para el bot
  • Control general para todos los bots en el sistema
  • Mejor registro de información común
  • Mejor registro de información especifica
  • Control personalizado de acuerdo al usuario
  • Integración con otras plataformas de bot (discord, twitch, etc)

Revisando los logs de mi server: w00tw00t Scan

Últimamente he tenido problemas con mis servidores, en cuanto tuve que restaurar a los valores de fabrica mi VPS interlan.dev y ahora tuve que abandonar mi servicio de hosting para interlan.ec, así que mientras descubría como valerme por mi mismo sin depender de un hosting compartido, he visto con curiosidad algunos registros de mi log.

En adelante, seguiré revisando los logs, siempre tienen cosas curiosas puesto que prácticamente el estar en internet es una vulnerabilidad. Aunque no sea un ataque explicito hacia mi server, los escaneos de vulnerabilidades ya cuentan como ataques.

En este caso, me he encontrado con la siguiente línea:

[21/Jun/2016:06:35:55 -0400] “GET /w00tw00t.at.blackhats.romanian.anti-sec:) HTTP/1.1” 400 0 “-” “ZmEu”

En un principio me pareció algo extraño así que me dispuse a investigar. Realmente no hay mucha información en internet sobre esto, pero hay, así que di con el sitio Arnon on Technology donde se explica lo siguiente

Las entradas w00tw00t son creadas por los escáneres de vulnerabilidades “ZmEu” o “DFind” como parte de un intento de ataque “banner grabbing”. Este ataque es una técnica de enumeración y en este caso, el escáner estaba buscando información sobre mi server que pueda revelar posibles “exploits”. El proceso va algo como esto: Un bot, posiblemente una computadora infectada o un servidor proxy, envía una petición “HTTP GET” con una dirección falsificada con la esperanza de que el servidor objetivo, responda con algo de información sobre sus configuraciones. En mi caso, “Nginx” determino que la petición “HTTP” estaba malformada de alguna forma, así que la rechazo con un código de error “400 Bad Request”. Al parecer, la petición carecía del encabezado “Host”, con la esperanza de que mi servidor rellene esta información, o sirva alguna otra que revele mas datos.

Aron on Technology

Habiendo visto que mi servidor también respondió con un “400 Bad Request” pude respirar tranquilo, sabiendo que al menos esta vez, mis configuraciones me han mantenido a salvo.

Hace bastante tiempo que no oía de los escáneres de vulnerabilidad, así que he investigado un poco. En la Wikipedia he encontrado esto, aunque solo en inglés:

“ZmEu” es un escáner de vulnerabilidades que busca servidores web que estén abiertos a ataques a través de “phpMyAdmin”. También intenta adivinar contraseñas “ssh” a través de métodos de fuerza bruta y deja un “backdoor” persistente. Fue desarrollado en Romania y fue especialmente común en el año 2012

Wikipedia

No quisiera tener que estar cambiando las contraseñas a cada rato, así que el siguiente paso seria agregar un “fail2ban” o usar archivos de llaves para “ssh”.

Siempre me pareció raro que reciba tantos ataques siendo que mi servidor, aparte de nuevo, nunca ha sido publicitado en ningún lado, pero sabiendo que es un escáner (muchos escáneres), tiene sentido que hayan llegado. Aunque seria genial si me dejaran en paz, tendré que ir viendo como reforzar la seguridad.

Interlan
A %d blogueros les gusta esto: