Migrar una lista de correo a Discourse (mbox, Listserv, Google Groups, etc)

Estoy intentando importar un volcado estándar mbox de una lista de correo, pero tengo problemas de “Proceso terminado”, generalmente después de mucho tiempo en la etapa de “indexación […]mbox”. Esto proviene de un archivo mbox grande de un proyecto de código abierto con diez años de publicaciones.

Cosas que he intentado:

  • Dividir el archivo mbox en fragmentos. Esto funcionó parcialmente e importó con éxito muchas publicaciones, pero ahora estoy atascado en la indexación de uno de estos fragmentos. Intenté dividir ese archivo en fragmentos también; el primero se importó finalmente, pero el segundo parece estar detenido ahora.

  • Aumentar la memoria disponible en nuestro servidor. El uso de memoria aumenta lentamente durante la indexación y actualmente se estabiliza alrededor de 16 GB (de 32 GB) para un intento de importación de uno de estos fragmentos, un archivo mbox de 80 MB:

Durante este tiempo, 1 CPU continúa al máximo.

Cualquier consejo sería muy apreciado, en particular aumentar la verbosidad de la salida de depuración si quizás se está atascando en una publicación en particular. El archivo index.db en la carpeta import tiene unos 800 MB.

Soy nuevo en Ruby y no uso SQL regularmente, por lo que me resulta difícil entender qué está sucediendo. Además, este servidor de 32 GB es caro, así que me gustaría reducirlo de nuevo a 4 GB pronto :slight_smile:

¡Gracias por cualquier ayuda!

1 me gusta

Supongo que el analizador se queda colgado en un correo electrónico concreto de ese mbox. El archivo index.db es una base de datos SQLite. Echa un vistazo a la tabla email, filtra por el nombre del archivo mbox en la columna filename y busca el valor más alto en la columna last_line_number. Es muy probable que el analizador se quede colgado en el siguiente correo electrónico después de ese número de línea dentro del archivo mbox.

3 Me gusta

Muchas gracias, @gerhard. He logrado identificar el último correo electrónico indexado con éxito y el siguiente (que asumo que es el que está causando la interrupción). Sin embargo, no parece haber nada excepcional en estos correos para mí. ¿Te parece bien si te envío estos dos correos de ejemplo en un mensaje privado para ver si algo destaca?

Claro, puedes enviarme un mensaje privado. Y prueba eliminando esos correos del archivo mbox para ver si la indexación funciona.

3 Me gusta

Gracias, enviado. No pude enviarte un mensaje privado directamente, así que lo envié a través del grupo del equipo; espero que esté bien. Intentaré quitar también el correo electrónico y ver hasta dónde llegamos antes de que vuelva a colgarse.

1 me gusta

Gracias por los correos electrónicos. No noté nada fuera de lo común y en mis pruebas funcionó sin problemas.

Esto no ha sido probado, pero podrías intentar aplicar el siguiente parche de git antes de ejecutar el script de importación. Agrega un tiempo de espera de 60 segundos para analizar un correo electrónico. Eso podría ayudarte a identificar el culpable y seguir adelante si solo afecta a un par de mensajes.

From 92efb4fc68724cfa20d5de48ba33b99c126a3a08 Mon Sep 17 00:00:00 2001
From: Gerhard Schlager
Date: Fri, 2 Oct 2020 17:27:39 +0200
Subject: [PATCH] Add timeout for parsing email in mbox importer

---
 script/import_scripts/mbox/support/indexer.rb | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb
index dc6e092c29..01523dad13 100644
--- a/script/import_scripts/mbox/support/indexer.rb
+++ b/script/import_scripts/mbox/support/indexer.rb
@@ -65,11 +65,15 @@ module ImportScripts::Mbox
     def index_emails(directory, category_name)
       all_messages(directory, category_name) do |receiver, filename, opts|
         begin
-          msg_id = receiver.message_id
-          parsed_email = receiver.mail
-          from_email, from_display_name = receiver.parse_from_field(parsed_email)
-          body, elided, format = receiver.select_body
-          reply_message_ids = extract_reply_message_ids(parsed_email)
+          msg_id = parsed_email = from_email = from_display_name = body = elided = format = reply_message_ids = nil
+
+          Timeout.timeout(60) do
+            msg_id = receiver.message_id
+            parsed_email = receiver.mail
+            from_email, from_display_name = receiver.parse_from_field(parsed_email)
+            body, elided, format = receiver.select_body
+            reply_message_ids = extract_reply_message_ids(parsed_email)
+          end
 
           email = {
             msg_id: msg_id,
-- 
2.28.0
3 Me gusta

Muchas gracias, @gerhard, tu parche está funcionando de maravilla. Para mis propósitos, creo que omitir los mensajes problemáticos está bien, ya que solo hay una cantidad pequeña; sin embargo, ahora tenemos una salida adicional si resulta útil para resolver el problema o para hacer que el script del importador sea más robusto:

Failed to index message in /shared/import/data/lammps-users/chunk_10.mbox at lines 726814-729353
execution expired
["/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:243:in `escape_text'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:214:in `serialize_node_internal'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:58:in `write_to'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:699:in `serialize'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:855:in `to_format'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:711:in `to_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `block in inner_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `map'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `inner_html'",
"/var/www/discourse/lib/html_to_markdown.rb:74:in `block (2 levels) in hoist_line_breaks!'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/lib/html_to_markdown.rb:57:in `block in hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `loop'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:16:in `initialize'",
"/var/www/discourse/lib/email/receiver.rb:387:in `new'",
"/var/www/discourse/lib/email/receiver.rb:387:in `select_body'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:74:in `block (2 levels) in index_emails'", 
"/usr/local/lib/ruby/2.6.0/timeout.rb:108:in `timeout'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:70:in `block in index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:139:in `block (2 levels) in all_messages'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:171:in `block in each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:190:in `block in each_line'",
 "/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:166:in `each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:132:in `block in all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `foreach'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:66:in `index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:25:in `block in execute'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `each'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `execute'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:43:in `index_messages'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:27:in `execute'", 
"/var/www/discourse/script/import_scripts/base.rb:47:in `perform'", 
"script/import_scripts/mbox.rb:12:in `<module:Mbox>'", 
"script/import_scripts/mbox.rb:10:in `<module:ImportScripts>'", 
"script/import_scripts/mbox.rb:9:in `<main>'"]

Como antes, puedo compartir el mensaje específico si resulta útil; esta vez, el mensaje de error me proporciona los números de línea específicos, por lo que al menos podemos tener una alta confianza de que hemos identificado el mensaje correcto.

4 Me gusta

Claro, compárteme los mensajes y los revisaré. Si hay un problema que podamos solucionar, no solo mejoraremos el importador, sino también Discourse en sí, ya que utiliza el mismo parser para los correos entrantes.

1 me gusta

He estado ejecutando este script diariamente durante los últimos meses para un sitio que realmente necesita cambiar a suscribir la categoría al grupo, pero eso aún no se ha hecho. Funciona bien, excepto que de vez en cuando necesito obtener un nuevo archivo cookies.txt. Hace aproximadamente un mes, sucedió algo y comenzó a quejarse diciendo: “Parece que no tienes permisos para ver las direcciones de correo electrónico. Abortando.” Hice… algo… y volvió a funcionar. Hace poco más de una semana sucedió de nuevo y he vuelto a descargar las cookies con múltiples navegadores y complementos de cookies, pero sigo obteniendo la versión de las publicaciones sin correos electrónicos. Puedo ver las direcciones cuando inicio sesión en el navegador web.

¿Alguien más ha tenido problemas últimamente? ¿Alguna idea sobre qué hacer? He intentado experimentar con qué dominios están en la llamada add_cookies del script, pero eso no ha ayudado.

1 me gusta

Bueno, estoy revisando esto de nuevo y parece que enlaces como

 https://groups.google.com/forum/message/raw?msg=GROUP_NAME/THREAD_ID/POST_ID

solían incluir las direcciones de correo electrónico completas, pero ahora ya no. Puedo confirmar que cuando inicio sesión, puedo hacer clic en «Acerca de» y ver las direcciones de correo completas en la interfaz web de Google Groups, pero si abro la URL anterior (la misma que usa el script de extracción) en el mismo navegador, los datos llegan con las direcciones de correo enmascaradas.

Supongo que habrán aumentado la privacidad o algo así.

Aquí hay otra pista: puedo abrir ese enlace en mi navegador y funciona, pero si copio el comando «copiar como cURL», la orden curl no obtiene las direcciones de correo. Suspiro. Bueno, lo intenté con otro navegador y el comando curl funcionó. No logro entender por qué el script no está obteniendo las direcciones de correo.

Así que quizás haya algún otro comportamiento específico del navegador que esté influyendo.

No lo he probado recientemente, por lo que es posible que hayan ocurrido cambios que el scraper no puede manejar en este momento.

@riking notó que Google Takeout exporta archivos mbox para los propietarios de los grupos, así que esa podría ser una opción a considerar.

5 Me gusta

Gracias. Bueno, hace una semana funcionó para un segundo sitio y luego actualicé mi archivo de cookies una vez más y descargué los datos del primer sitio. Parece que solo funcionó por un día o dos, y ahora nuevamente no funciona. Ni para un sitio ni para el otro. Veo la dirección de correo electrónico completa en mi navegador, descargo las cookies de esa pestaña, pero no hay resultados.

Voy a revisar el takout. EDICIÓN: Bueno, para obtener el archivo mbox parece que necesitas ser un administrador super, no solo un propietario.

4 Me gusta

Una herramienta de línea de comandos para convertir una lista de correo de Mailman2 (es decir, el contenido del config.pck con opciones, miembros, moderadores, marcadores privados o públicos, etc.) en una categoría de Discourse está disponible aquí: Client Challenge

1 me gusta

@gerhard ¿Tienes alguna idea sobre cómo adaptar estas instrucciones para usar una instalación de desarrollo en lugar de la instalación estándar? Siento que he estado muy cerca de lograr una migración de listserv usando solo unos pocos comandos, pero no puedo hacer que funcione lo que supongo que es el último paso, ya sea con:

ruby /src/script/import_scripts/mbox.rb ~/import/settings.yml
bundle exec ruby /src/script/import_scripts/mbox.rb /home/discourse/import/settings.yml

Ambos fallan al cargar todas las dependencias. Mira aquí el conjunto completo de comandos que usé y los errores. ¿Alguna idea? ¿Faltarán algunas llamadas a d/bundle?

Lo siguiente que probaré es usar una máquina virtual Ubuntu y hacer una “instalación estándar” allí, pero esto parece un poco excesivo dado que la instalación dev funciona bastante bien de todos modos.

Soy un completo novato en discourse (y en ruby, y sobre todo en docker), así que disculpa si esto es obvio o (peor aún) irrelevante.

3 Me gusta

Parece que ya has resuelto la mayor parte.

No he probado esto con la instalación de desarrollo basada en Docker, pero supongo que necesitas agregar "gem 'sqlite3'" al Gemfile y ejecutar apt install -y libsqlite3-dev dentro del contenedor antes de ejecutar d/bundle install.

Después, bundle exec ruby ... debería funcionar.

3 Me gusta

@gerhard gracias por el suave recordatorio: he vuelto a ejecutar desde cero absoluto (desde git clone en adelante) añadiendo gem 'sqlite3' al final de /src/Gemfile, ya que asumí que era esa la que te referías, ¡y funcionó! Por si acaso, aquí están las instrucciones que usé (para la lista de correo mne_analysis):

1. En el host Ubuntu

git clone https://github.com/discourse/discourse.git
cd discourse
d/boot_dev --init
d/rails db:migrate RAILS_ENV=development
d/shell
vim /src/Gemfile  # añadir gem 'sqlite3' al final
exit
d/bundle

2. En la shell de Docker

sudo mkdir -p /shared/import/data
sudo chown -R discourse:discourse /shared/import
wget -r -l1 --no-parent --no-directories "https://mail.nmr.mgh.harvard.edu/pipermail//mne_analysis/" -P /shared/import/data/mne_analysis -A "*-*.txt.gz"
rm /shared/import/data/mne_analysis/robots.txt.tmp
gzip -d /shared/import/data/mne_analysis/*.txt.gz
wget https://gist.githubusercontent.com/larsoner/940cd6c7100b87c4c5668cb0bc540afb/raw/9e78513620d11355ad0e10f4a2470996c26ebc8c/mailmanToMBox.py -O ~/mailmanToMBox.py
python3 ~/mailmanToMBox.py /shared/import/data/mne_analysis/
rm /shared/import/data/mne_analysis/*.txt
sudo apt install -y libsqlite3-dev  # sin efecto para mí

# verificar resultados
cat /shared/import/data/mne_analysis/*.mbox > ~/all.mbox
sudo apt install -y procmail
mkdir -p ~/split
export FILENO=0000
formail -ds sh -c 'cat > ~/split/msg.$FILENO' < ~/all.mbox
rm -rf ~/split ~/all.mbox

# configuraciones
wget https://raw.githubusercontent.com/discourse/discourse/master/script/import_scripts/mbox/settings.yml -O /shared/import/settings.yml

# ejecutarlo
cd /src
bundle exec ruby script/import_scripts/mbox.rb /shared/import/settings.yml

Esto produjo mucha información útil y al final:

...
Actualizando temas destacados en categorías
        5 / 5 (100.0%)  [6890 elementos/min]   ]  
Reiniciando contadores de temas


Hecho (00h 06min 21seg)

Luego salí y en el host Ubuntu:

d/unicorn &
google-chrome http://0.0.0.0:9292

¡Listo!

Probablemente ajustaré la configuración para eliminar el prefijo [Mne_analysis], ¡pero estoy encantado de que ya funcione tan bien!

4 Me gusta

@gerhard ¿Se puede usar tu importador de mbox solo al instalar Discourse por primera vez, o también se puede usar más tarde cuando ya hay otros usuarios utilizando Discourse? Si el importador se usa mientras Discourse está siendo utilizado por otras personas, ¿experimentarán algún efecto secundario?

1 me gusta

Para que el importador pudiera extraer mensajes de Google Groups, tuve que revertir este cambio en /script/import_scripts/google_groups.rb
https://review.discourse.org/t/fix-google-groups-import-changed-login-url-9432/10615
Volví a colocar la línea

    wait_for_url { |url| url.start_with?("https://accounts.google.com") }

en

    wait_for_url { |url| url.start_with?("https://myaccount.google.com") }

De lo contrario, cada vez que ejecutaba el proceso obtenía este mensaje:

Iniciando sesión...
Error al iniciar sesión. Por favor, verifica el contenido de tu archivo cookies.txt
6 Me gusta

@gerhard Noté que después de la importación, aunque los mensajes parecen correctos, no hay ningún usuario en espera, a pesar de que debería haberlos (usé staged: true por defecto). La salida es la siguiente:

...
indexando respuestas y usuarios

creando categorías
        1 / 1 (100.0%)  [13440860 elementos/min]  
creando usuarios

creando temas y publicaciones
     7399 / 7399 (100.0%)  [1421 elementos/min]     
...

¿Se supone que también debería mostrarse un contador de usuarios?

También intenté ejecutar con staged: false y se mostró la misma salida; además, ninguno de los usuarios de la lista de correo pertenece a ningún grupo. Por si ayuda ver qué se está procesando realmente, aquí hay uno de los muchos archivos .mbox que se están importando:

2020-December.zip|adjunto (49.5 KB)

La única configuración no predeterminada fue agregar:

tags:
  "Mne_analysis": "mne_analysis"

Sería genial que estos usuarios aparecieran como en espera para que puedan reclamar sus publicaciones antiguas al registrarse. ¡Cualquier consejo o idea será muy apreciado!

1 me gusta

¿Probablemente debería aceptar ambas?