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.

No hay comentarios:

Publicar un comentario