17 años en Internet

31 diciembre, 2021

SeaFile: La upgrade imposible (migrando de 4.3 a 7.1)

     En septiembre de 2015 (hace 6 años en el momento que escribo esta entrada), me monté una nube casera haciendo uso de una Raspberry PI B+ (procesador ARMv6 700 MHz a 32 bits, 512 MB de memoria RAM, Ethernet 10/100 y 4 puertos USB 2.0) y un viejo disco duro externo de 1 TB (un MyBook de WD de 2011). Como software, utilicé la Raspbian de la época (basada en Debian 8, aka Jessie), la versión “community” 4.3 de SeaFile configurado para tener una BD en formato SQLite y le puse un fork de Apache como frontal configurado para tener activado el fastCGI. Como particularidad, la raspberry se alimentaba por el puerto usb del router wifi (entonces consumían menos voltaje) y la carpeta “seafile-data” (que contiene los documentos de la nube y la base de datos en formato SQLite) era un enlace simbólico apuntado al punto de montaje del disco duro externo.

 
    Lo bueno de tener un enlace simbólico es que, si el día de mañana se te queda pequeño el disco de 1 TB, la bascula a uno de mayor capacidad no es compleja: Paras los servicios de SeaFile, te aseguras que el nuevo disco tenga el mismo formato, copias todo el contenido de un disco al otro, editas el /etc/fstab, remontas las unidades, reinicias los servicios de SeaFile y la magia vuelve a actuar.

Lo compré en 2011 y sigue vivo. Una década ya.

    Otra particularidad es que a nivel de router la raspberry tenía una IP fija en mi red local y que había definido una serie de mapeados de puertos de forma de que todo lo que entrara por el puerto XXXX fuera redirigido al puerto 443 (sí, le puse un certificado) de la raspberry. Además, me hice con un dominio que reenviaba a la IP estática de mi router, de forma si salía de casa, pudiera siempre acceder a mi nube. Y lado en casa, edité los /etc/hosts (para los windowseros, sabed que también tenéis uno en C:/Windows/System32/drivers/etc) de forma que ese dominio se transformara en la IP local de la raspberry y así no sacar el flujo a fuera de casa. Lo bonito habría sido configurar esto último a nivel del router, pero bueno, me encontré con una serie de limitaciones.


     Y así ha ido todo bonito y níquel durante estos 6 años. De vez en cuando se me moría la tarjeta SD de la Raspberry (pensad que es un server encendido 24x7 y que estas tarjetas no están pensadas para ser durables), pero tirando de backups, cada dos años le ponía una nueva y arreando. Total, si la conf del server no cambia, posées la imagen de la SD y los ficheros importantes son enlaces simbólicos a dispositivos externos, la restauración rara vez era complicada. Y comprar una tarjeta cada dos años tampoco es que fuera una gran ruina.

 

Mi Raspberry cada vez que le metía una SD nueva.


    Pero claro, todo este sistema tenía una serie de inconvenientes que al principio podía pasar por alto, pero que conforme pasan los años se volvían algo irritantes. Por ejemplo: El conjunto, o eso pensaba, de tener un voltaje bajo (carecía de adaptador de corriente para la raspberry) y utilizar el cable de ethernet “barato” pues hacía que en la práctica los uploads y downloads de ficheros tuvieran un cuello de botella de 2 MB/s. Esta velocidad, para lo que es el trabajo del día a día no es una gran molestia, pero en el momento que te compras un PC nuevo resulta desesperante (intenta bajarte unos 100 GB de documentos a esa velocidad).


    Así que todo esto que os voy a comentar empezó como una queja de “la nube va lenta” y me puse a indagar y a troubleshootear para ver si esto no se podía mejorar:

  • Comprobé que no se trataba de un tema de alimentación (voltaje insuficiente), puesto que seguía teniendo estas velocidades incluso habiendo conectado la raspberry a un adaptador de corriente (en vez de chupar del router).
  • Comprobé que no era una limitación del cable ethernet, probando con uno de 100 MB/s y con otro a gigabit.
  • Comprobé que no era un tema de tener enchufado el disco duro por USB, puesto que usaba el cable del fabricante y este estaba especificado como 2.0.
  • Llegué a enchufar un adaptador wifi por USB, para ver si podía ir mejor…

    Pero no, iba probando y probando y llegaba siempre al mismo máximo de subida y bajada de 2 MB/s. Me volvía paranoico, empecé a buscar si no se trataba de alguna configuración a nivel del OS o del SeaFile… Pero nada.
 

     No era lógico: La teoría decía que, si mi Raspberry tenía una ethernet de 10/100, la red local por ethernet la tengo a gigabit y el disco duro iba por USB 2.0, debería a velocidades de por lo menos 20 MB/s.

    No daba con la tecla. Así que me puse a googlear y vi que no era el único usuario que se quejaba de las velocidades de SeaFile y que parecía ser que se “corregía” haciendo una subida de versión (es decir, actualizando a una versión más reciente). No estaba nada claro, quiero decir, había usuarios que decía que les iba perfecto y otros remontaban la misma limitación que yo (2 MB/s).
 

    Así que puestos a upgradear, me bajé la última versión 7 de SeaFile (la v7.1.5) para Raspberry PI, dando por hecho de que se trataría de una versión estable (la siguiente es la v8 y tengo prevista instalarla también) y sobre el papel realizar la upgrade no parecía difícil:

  • Paras el servicio
  • Cambias el enlace simbólico de seafile-server-latest para apuntar a la 7.1.5.
  • Entras en la carpeta de seafile-server-latest/upgrades y ejecutas uno a uno todos los scripts de migración. En mi caso, para pasar de 4.3 a 7.1.5 tenía que ejecutar los scripts siguientes: upgrade_4.3_4.4.sh, upgrade_4.4_5.0.sh, upgrade_5.0_5.1.sh, upgrade_5.1_6.0.sh, upgrade_6.0_6.1.sh, upgrade_6.1_6.2.sh, upgrade_6.2_6.3.sh, upgrade_6.3_7.0.sh y upgrade_7.0_7.1.sh
  •  Relanzas el servicio.

    Claro, una cosa es la teoría, pero la práctica… Ejecuto al primer script y este peta. ¿Por qué? Pues por algo increíble: Los scripts verifican la versión de Python que tiene instalado el OS y si es menor a 2.7 no te dejan ejecutarlos… Pero el problema es que los scripts están escritos en Pyhton 3, los cuales no son compatibles con Python 2.


     Ante este panorama tuve que instalar Python 3.7 via apt-get, borrar el /usr/bin/python y crear un enlace simbólico para que /usr/bin/python apunte a /usr/bin/python3.7.
 

    Una vez hecho esto, me pongo a ejecutar los scripts de upgrade y todos funcionan a la perfección. Acto seguido, reinicio el servicio… ¡Y PAM! Pete: El script hace uso de binarios incompatibles. Claro, al leer esto mi reacción fue “¿cómo es posible? ¿no me había bajado la versión arm de raspberry pi?”.
 

    Así que hago un “uname -a” para ver el procesador (armv6l) y entro en la página web de SeaFile para comprobar qué me había bajado… Y resulta que no había versión de armv6l. La versión por defecto para ARM eran la arm64 y la armv7, la cuales no eran compatibles.
 

 

    Al ver esto me morí de miedo: Tenía una base de datos actualizada a 7.1.5 y no podía hacer uso de los binarios de 7.1.5 debido a una incompatibilidad de hardware (concretamente a nivel de procesador). Además, los scripts carecían de rollback posible y temía que si me ponía a ejecutar ahora los binarios de la 4.3 corrompería varios centenares de gigas de datos.
 

    La siguiente opción fue bastante obvia para todo linuxero: Bajarte las fuentes de la 7.1.5 y compilar tú mismo. Pero nada, que no había forma. Los scripts de building son bastantes completos, te instalan todas las dependencias, Python hace su magia con wheels… pero estaba ante un impass: Sencillamente la solución no compilaba en armv6l. No había forma, relanzaba, relanzaba, hacía uso de pip3… Pero nada, que el building no quería ir. Había que cambiar de hardware, no había otra opción posible.
 

 - "Parece que tocará gastarse pelas."

    Así que animado por un video del youtubero TuberViejuner, acabé comprando una Raspberry PI 400: ARM v8 de 4 núcleos a 64 bits y 1,8 GHZ, 4 GB de RAM, puerto ethernet, 2 puertos USB 3.0 y uno 2.0… Vamos, todo un obús comparado con el modelo anterior. Tuve la suerte de hacerme con el kit de programación a un precio bastante barato (flipa con las reventas, se les va la pinza) y ésta me vino ya con el OS preinstalado: Una Raspbian basada en Buster (Debian 10).
 


     Me puse a tunear el sistema operativo y claro, notas rápido la diferencia de seis años: El fork de Apache que utilizaba para utilizar fastCGI ya no existía, los módulos han cambiado de nombre… Total, que me encontraba con una conf de apache que tuve que readaptar a mi gusto (no basta con hacer un copy-paste de los available-sites o del /etc/apache2 completo).
 

    Después enchufé el disco duro externo y edité el fstab a mi gusto y una vez tenía todos los prerrequisitos instalados y un apache funcional, decidí intentar arrancar el SeaFile 7.1.5 sobre esta nueva raspberry… Y cómo no, petó.
 

    ¿Por qué? Porque mis scripts fuerzan el inicio del fastCGI y SeaFile ya no era compatible con fastCGI. Total, me tocó modificar mis scripts y la conf de Apache para quitar el fasfGCI (lo cual no era complicado) y relanzar… Y esta vez sí, parecía que arrancaba.
 

    Así que abro el navegador y miro a ver si la interfaz web estaba disponible… Pero no, no lo estaba. Me saltaba una excepción de Apache y las trazas de este se limitaban a decir que había un problema con el backend (SeaFile). Abro un cliente de SeaFile, intento hacer un login y me devuelve un error de inicio de sesión.


    Así que me pongo a ver las trazas de seafile (lado server) para troubleshootear y descubro que remontan excepciones acerca del cifrado, quejándose de que faltan librerías, dando a entender que faltaba algún tipo de dependencia en “seafile-server-latest/seahub/thirdpart”. A base de comandos de pip verifico que las bibliotecas de crypto y pycrypto están presentes, intento de nuevo, las trazas arrojan siempre el mismo error… Hasta que googleando me doy cuenta que esas librerías están desactualizadas y que a efectos prácticos están siendo remplazadas por el paquete pycryptodome. Así que haciendo uso de pip instalo pycryptodome y al relanzar consigo ver por fin la web de SeaFile y en los clientes consigo hacer login y todas las bibliotecas salen reflejadas.
 

    “Ok, parece que va”… Así que me voy a dormir tranquilo y al día siguiente descubro que ninguna biblioteca se sincroniza: Nada sube a la nube y nada baja de ella. ¿Cómo es posible? Me pongo a ver las trazas de SeaFile y detecto excepciones ligadas a una tabla inexistente en la base de datos: RepoFileCount.
 


    Me pongo a googlear y acabo en un hilo donde otros usuarios comentan el mismo problema que yo: Han hecho una subida de versión, consiguen loguearse, pero ninguna biblioteca se va sincronizando y además, al conectarse a la interfaz web comentan que se van creando bibliotecas vacías. Ahí un usuario responde que el problema parece que es que no se han ejecutado todos los scripts de la v6 (upgrade_6.1_6.2.sh, upgrade_6.2_6.3.sh y upgrade_6.3_7.0.sh).
 

     Pero claro, yo ese análisis lo pongo en duda, debido a que estoy seguro de que los he ejecutado (bendito bash_history) y porque si pillas los scripts en SQL ves que se hacen varios ALTER TABLE, pero no sale reflejado ningún CREATE TABLE :

pi@raspberrypi:seafile-server-latest/upgrade/sql $ grep -R Count *
6.3.0/mysql/seafile.sql:ALTER TABLE RepoFileCount DROP primary key;
6.3.0/mysql/seafile.sql:ALTER TABLE RepoFileCount ADD id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
6.3.0/mysql/seafile.sql:ALTER TABLE RepoFileCount ADD UNIQUE (repo_id);


     Total, que desde mi punto de vista esa tabla se ha creado nueva entre la 4.4 y la 6.3.0 y en ningún momento se ha hecho su CREATE TABLE en los scripts de migración. Por suerte, los diferentes CREATE TABLE de MySQL y SQLite pueden encontrarse en la carpeta seafile-server-latest/sql:

pi@raspberrypi:seafile-server-latest/sql $ grep -R Count *
mysql/seafile.sql:CREATE TABLE IF NOT EXISTS RepoFileCount (
sqlite/seafile.sql:CREATE TABLE IF NOT EXISTS RepoFileCount (repo_id CHAR(36) PRIMARY KEY, file_count BIGINT UNSIGNED);

    Así que con un cliente de sqlite me conecté a la BD de mi Seafile y ejecuté manualmente el CREATE TABLE:

main: seafile.db
sqlite> .tables
Branch RepoHead RepoTrash UserShareQuota
GarbageRepos RepoHistoryLimit RepoUserToken VirtualRepo
InnerPubRepo RepoInfo RepoValidSince WebAP
OrgQuota RepoOwner SeafileConf WebUploadTempFiles
OrgUserQuota RepoSize SharedRepo
Repo RepoSyncError SystemInfo
RepoGroup RepoTokenPeerInfo UserQuota
sqlite> CREATE TABLE RepoFileCount (repo_id CHAR(36) PRIMARY KEY, file_count BIGINT UNSIGNED);
sqlite> .exit

    Y tras hacer esto, SeaFile volvió a funcionar perfecto: Podía hacer login, descargar bibliotecas, todo se sincronizaba, alcanzaba velocidades de entre 20-50 MB/s de subida y bajada (y seguramente iría mucho mejor si usara un disco duro externo por USB 3.0). Vamos, que todo iba bien y perfecto.

    Pero claro, con tanto troubleshooting me vino una desagradable sorpresa: Se me llenó el disco de un tera. Durante mis pruebas hice un borrado de una biblioteca pesada y la volví a cargar. Pero claro, las cuentas no me salían: Si una carpeta pesa X, otra carpeta tiene Y, he quitado la opción de histórico… ¿por qué el disco duro se ha llenado si debería de tener unos 300 GB disponibles?
 


     Así que me puse a indagar y resulta que SeaFile no borra datos incluso si tú los borras. Cada modificación de fichero o cada fichero borrado sigue ocupando su espacio en disco en el servidor. SeaFile se comporta como un repositorio de revisiones, como GIT o SVN, cada modificación tiene su referencia en hash y su contenido en blob y cuando tú borras un fichero o quitas la historización, el blob y el hash quedan huérfanos de referencia, pero siguen existiendo en el disco.
 
 
    Por suerte, existe un script dentro de seafile-server-latest llamado “seaf-gc.sh” que lo que hace es buscar todos esos nodos huérfanos y borrarlos del disco. Y bueno, para lanzarlo tienes que parar antes el servicio. En mi caso, tras lanzar el script gané como unos 275 GB de espacio (imaginaos, unos 7 años sin hacer “limpieza”). Una vez descubierto esto preparé un script de parada + limpieza + reinicio y se lo endosé a un crontab para que se lanzara una vez cada 15 días.


    Y poco más, tras muchas horas y varias noches de sufrimiento, por fin tengo de nuevo mi nube disponible y me está funcionando mejor que nunca.

27 diciembre, 2021

[Blender] [WIP] Ranma "Chan", semana 2

    Esta semana ha sido un poco light (apenas le habré dedicado 2 horas a Blender) y el poco tiempo que he invertido lo he dedicado a retrabajar los ojos, remodelar la cabeza y añadir mechones para intentar ir dándole forma al famoso moño de Ranma.


    Para los ojos partí de un cubo con subidivisiones para darle aspecto rodeado y fuí mondeándolos a la imagen de referencia. Después los coloqué en la cara (con un modificador de espejo) y modelé esta última para readaptar los huecos.



    Para el iris pillé un plano y le apliqué un modificador de espejo en los ejes X y Z para darle simetría (haces una esquina y el resto salen idénticas). Modelé el iris siguiendo las proporciones de la imagen de referencia, le hice un extrude interior a nivel del eje Y para darle cierta profundidad (dar sensación de hundimiento), le puse un modificador de subdivisión para suavizar y texturicé a mano el material. Y por último jugué con el modificador de shrinkwrap para abatir el plano al ojo.





    Para las pestañas y párpados partí de planos con modificador de solidify y fuí modelando a mano alzada con simples extrudes y reescalados.






    Después me puse a remodelar la cara para hacerlo más "ánime-like". Para ello le di una pulida al modelo por el modo escultura e hice modificaciones manuales en determinados vértices.




    Después, haciendo uso de bezier circles y nurbs paths me puse a trabajar en los mechones del moño.




    Y por último improvisé unas patillas, partiendo de un cubo al que le apliqué un modificador de subdivisiones y varios loop cuts que me sirvieron para ir modelando las formas.




    Y poco más, el lunes de la semana que viene os iré compartiendo mis avances con este modelo.



21 diciembre, 2021

Microsoft detalla la problemática de "las 3 luces rojas" en un documental oficial [ENG]

[Blender] [WIP] Ranma "Chan", semana 1

 


    Aquí os comparto mis avances con el modelo de Ranma, protagonista de una de las mejores series de Rumiko Takahashi y personaje con el que he empezado a trabajar la semana pasada.

    Para la realización del torso partí de dos cubos modificando la silueta siguiendo varios concepts arts de la serie original, al que le sumé un cubo con subdivisiones para hacer el busto.



 
    Junté las piezas con un modificador de boolean, apliqué subdivisiones y loop cuts y hice uso de extrudes para darle forma a las extremidades.





    Después repasé los vértices del torso para darle una forma más realista y le di un repaso por el modo escultura.




    Para las manos, pies y cabeza reutilicé partes de otros modelos que ya hice y las deformé para darles las proporciones que se aprecian en los diferentes concepts arts del ánime, dando como resultado la primera versión del block-out del personaje.


     Después utilizando cubos básicos y planos me puse a hacer el pelo, los ojos, las cejas, las orejas, la coleta... Realmente no vale la pena dar muchos detalles: Los cubos tienen loop cuts y subdivisiones y para la oreja le hice un extrude interior y un repaso por el modo de escultura.








    Para la camisa partí de un cubo (como siempre), al que apliqué más loop cuts (como siempre) y un modificador de subdivisión. Esta vez no hice uso de ningún "Shrinkwrap" y me dediqué a acercar la prenda manualmente: La idea no es que la prenda esté ceñida, si no que le apliquemos más adelante un simulador de ropa para darle un aspecto más realista, son sus arrugas y su gravedad.




    El cinturón no tiene ningún secreto: Cubo hueco + subdivisiones.



    Los botones y el bordecillo son figuras básicas con un modificador de "Shrinkwrap" para que se encuentren siempre pegados a la camisa.


    Después estuve como una hora jugando con los parametros del cloth simulator para darle un aspecto más de "tela" a la camiseta y acto seguido improvisé unos pantalones (como siempre: Cubo hueco + extrudes, loop cuts y subdivisiones y esta vez sí, un modificador de "Shrinkwrap").





    Y por último estuve improvisando/jugando con los mechones de pelo, pero sin llegar a un resultado satisfactorio.


    Y poco más. La verdad es que este modelo me está dando una sensación bastante bipolar: Me gusta por ejemplo cómo me ha quedado el torso, pero no la cara y seguramente acabe rehaciendo por completo la cabeza, los ojos, el pelo... En fin, tened en cuenta que de normal dedico a mis modelos entre 4 y 8 semanas y que en éste apenas he dedicado una, por lo que aún quedan muchas cosas por pulir y mejorar.

    Si os interesa cómo está quedando o símplemente tenéis curiosidad, os invito a volver la semana que viene para compartiros nuevos avances.