Migrer un forum vBulletin 4 vers Discourse

Je suis seulement un nouveau converti à Discourse, alors après beaucoup d’essais et d’erreurs, j’ai combiné tout ce qui précède en une liste complète de commandes (merci à @titusca et @enigmaty).

J’espère que cela aidera (ou du moins accélérera) les nouveaux venus à passer du début à la fin. J’aimerais intégrer cela dans le premier message étant donné les mises à jour de mysql->mariadb qui, je pense, ont semé beaucoup de confusion dans le processus.

Contexte :

  • Transfert de 1,6 million de messages.
  • Utilisation d’un Droplet Digital Ocean (CPU Optimized 4 vCPU/8 Go)

#1 - Installer le Droplet Discourse 1-clic de Digital Ocean

#2 - Terminer l’installation de Discourse via SSH en suivant les invites

Ouvrir la console SSH
root
(votre_mot_de_passe_root)
(entrée)
(votre_domaine).com
(etc…)

#3 - Se connecter à SFTP pour télécharger le dump de base de données

sftp root@XXX.XXX.XX.XX
y
yes
(votre_mot_de_passe_root)
put db.sql /var/discourse/shared/standalone/db.sql

#4 - Se connecter au nouveau site Discourse pour configurer le compte administrateur

#5 - Se connecter à SSH - début du processus

ssh root@XXX.XXX.XX.XX
cd /var/discourse
./launcher start app
docker exec -it app bash
sudo apt-get update
sudo apt-get upgrade
y

#6 - Installer MariaDB (remplacement de mysql)

apt-get update && apt-get install mariadb-server-10.3 libmariadbd-dev
y

#7 - Configuration de la base de données Mysql

service mysql start
mysql -u root -p
mot_de_passe
create database vbulletin;
exit;

#8 - Transfert de Vbulletin vers la base de données Mysql

mysql -u root -p vbulletin < /shared/db.sql
mot_de_passe

#9 - Fichier GEM

echo “gem ‘mysql2’” >>Gemfile
echo “gem ‘mysql2’, require: false” >> /var/www/discourse/Gemfile
echo “gem ‘php_serialize’, require: false” >> /var/www/discourse/Gemfile
cd /var/www/discourse
su discourse -c ‘bundle install --no-deployment --without test --without development --path vendor/bundle’
(Ignorer le résultat en texte rouge)

#10 - Configurer le script d’installation

vi /var/www/discourse/script/import_scripts/vbulletin.rb

#10.a - Apporter les modifications nécessaires au fichier texte

DB_HOST ||= ENV[‘DB_HOST’] || “localhost”
DB_NAME ||= ENV[‘DB_NAME’] || “vbulletin”
DB_PW ||= ENV[‘DB_PW’] || “mot_de_passe”
DB_USER ||= ENV[‘DB_USER’] || “root”
TIMEZONE ||= ENV[‘TIMEZONE’] || “America/Los_Angeles”
TABLE_PREFIX ||= ENV[‘TABLE_PREFIX’] || “”
ATTACHMENT_DIR ||= ENV[‘ATTACHMENT_DIR’] || ‘/shared/attachments/’

#10.c - Fin des modifications

:wq

#11 - Configuration de Bundle

bundle config set path ‘vendor/bundle’
bundle config set without ‘development:test’
bundle config unset deployment
su discourse -c ‘bundle install’

#12 - Configuration de Mysql (il est peut-être possible de le faire avec l’étape précédente)

mysql --version
sudo mysql -u root -p
mot_de_passe
ALTER USER 'root'@'localhost' IDENTIFIED BY 'mot_de_passe';
FLUSH PRIVILEGES;
exit

#13 - Script d’installation

su discourse -c ‘bundle exec ruby script/import_scripts/vbulletin.rb’

Bonne chance !

8 « J'aime »

Je voulais simplement laisser un retour après notre migration depuis vB4 :

  • CORRIGÉ [s]Les publications supprimées en douceur n’étaient pas correctement masquées : https://github.com/discourse/discourse/pull/12057[/s]
  • [ul] + [li] et les [LIST] imbriqués n’ont pas été migrés correctement, et le plugin BBcode ne semble pas non plus gérer cela → Cela semble être attendu : CommonMark testing started here! (Citation : Le cœur n’implémentera pas le support de [ul], [ol] et [li] pour BBcode car c’est une recette pour l’échec.) → Je devrai créer une magie RegEx pour un correctif postérieur.
  • Nous avons effectué une migration initiale en utilisant l’importateur normal (cela a pris > 3 jours) et avons redémarré la migration avec des instantanés de base de données plus récents à plusieurs reprises pour maintenir l’importation « fraîche » et réduire le temps d’arrêt à effectivement 30 minutes. Cette procédure a très bien fonctionné, sauf pour tout ce qui a été édité après notre importation initiale des fils et des publications. Nous devons maintenant retravailler manuellement ces informations.
  • Créer des plugins pour Discourse est vraiment difficile en raison du manque de documentation et d’une vue d’ensemble du fonctionnement de la structure des dossiers. Cependant, cela devient plus agréable et meilleur une fois que vous comprenez comment cela fonctionne.

Questions qui me restent :

  • Je ne suis pas sûr de la façon dont l’importateur mappe les publications déjà importées et comment faire correspondre l’ancien post_id vB4 au nouveau post_id Discourse pour masquer ces publications « supprimées en douceur ». Si quelqu’un peut me donner un indice, ce serait très bienvenu ! Trouvé : import_id à l’intérieur de la table post_custom_fields. Bien. Maintenant, je dois écrire un script pratique pour corriger cela :slight_smile: → Edit : Une meilleure méthode consiste à utiliser le script d’importation, qui mappe tous les identifiants importés pour une utilisation facile.
2 « J'aime »

Malheureusement, je ne peux pas modifier mon message précédent :slight_smile:

J’ai découvert un autre problème : chaque pièce jointe qui n’est pas liée à un message ne sera pas disponible pour Discourse.

Ma proposition de correction (PR) pour résoudre ce problème : FIX: vBulletin importer should import unreferenced attachments by paresy · Pull Request #12187 · discourse/discourse · GitHub

Merci !

3 « J'aime »

Un petit suivi concernant ma liste de problèmes. J’ai résolu le problème de visibilité.

Videz tous les posts affectés de votre ancienne base de données vBulletin :

SELECT postid
FROM `vb4_post`
WHERE `visible` > '1'
ORDER BY postid

Créez un fichier imported_post_ids.txt contenant tous les postid, un par ligne.

Créez un nouveau fichier pour le script de correction :

nano script/import_scripts/fix_visibility.rb 

Contenu :

require_relative '../../config/environment'
require_relative 'base/lookup_container'

@lookup = ImportScripts::LookupContainer.new

broken_postids = []
broken_real_postids = []

File.foreach("imported_post_ids.txt") do |line|
  broken_postids.append(line.to_i)
end

broken_postids.each do |id|
  broken_real_postids.append(@lookup.post_id_from_imported_post_id(id))
end

broken_real_postids.each do |id|
  puts id
  Post.find(id).trash!
end

Exécutez le script :

su discourse -c 'bundle exec ruby script/import_scripts/fix_visibility.rb'

Le script utilisera la logique de l’importateur pour mapper les postid importés vers les postid Discourse correspondants que nous souhaitons masquer.

4 « J'aime »

Salut tout le monde,

J’ai lancé le script pour une migration vb3. Je procède étape par étape et il traite actuellement 122 000 utilisateurs à 330/minute. Ensuite, nous aurons 2,5 millions de messages à traiter.

Nous faisons cela sur un serveur de production. Personne n’utilise le site discourse, nous venons de le mettre en place et il est à une URL anonyme. Si je me connecte, je peux voir les notifications de nouveaux utilisateurs s’incrémenter. C’est probablement une question stupide, mais je me demande si la migration serait plus rapide si nous suspendions ou désactivions le site en direct d’une manière ou d’une autre ?

1 « J'aime »

Cela dépend de la charge et du nombre de processeurs de votre serveur de production. Vous pouvez toujours essayer d’arrêter le serveur web pendant 5 minutes et voir si l’importation est plus rapide.

3 « J'aime »

L’importation prend vraiment du temps. Autant que je sache, l’importateur en masse devrait être plus rapide. Nous avons effectué une première importation à partir d’une sauvegarde sur notre machine de développement performante, puis une importation incrémentielle à partir d’une autre sauvegarde pour passer à Discourse avec seulement une demi-heure d’interruption. Méfiez-vous des choses qui peuvent mal tourner lors des mises à jour incrémentielles :slight_smile: (Voir ici : Migrate a vBulletin 4 forum to Discourse - #132 by paresy)

paresy

3 « J'aime »

Je vois un cœur sollicité qui, je pense, est le serveur qui ingère les données mises à jour, et un autre cœur sollicité lors de l’exécution du script d’importation. Je n’ai pas vraiment les connaissances du domaine pour savoir si la compétition entre ces deux processus pour la ressource de base de données pourrait ralentir l’importateur, et je n’ai pas non plus les connaissances du domaine pour savoir s’il est même possible d’arrêter l’ingestion tout en laissant le conteneur actif. L’ingestion doit se faire de toute façon, donc je suppose que la chose la plus sûre à faire est de la laisser continuer à tourner.

Un conseil pour les futurs lecteurs, je vois que 27k (22% !) de nos utilisateurs sont des spambots bannis. Nous les purgerons du côté source avant de faire l’importation finale.

[ajout] Une modification nécessaire qui n’est pas mentionnée ci-dessus :

--- a/script/import_scripts/vbulletin.rb
+++ b/script/import_scripts/vbulletin.rb
@@ -134,6 +133,7 @@ EOM
        , usertitle
        , usergroupid
        , joindate
+       , lastvisit
        , email
        , password
        , salt

Et une modification qui pourrait être spécifique à vb3 :

--- a/script/import_scripts/vbulletin.rb
+++ b/script/import_scripts/vbulletin.rb
@@ -987,7 +989,7 @@ EOM
   end

   def parse_timestamp(timestamp)
-    Time.zone.at(@tz.utc_to_local(timestamp))
+    Time.zone.at(@tz.utc_to_local(Time.at(timestamp)))
   end

[ajout] L’importation s’exécute sur une instance Oracle Cloud Ampere à 4 cœurs. À titre de comparaison, j’ai installé un serveur de développement Discourse localement/nativement sur un MacBook Air M1 et j’ai été surpris que le processus d’importation soit nettement plus lent.

6 « J'aime »

Rencontriez-vous des erreurs avec le script existant ? J’ai perdu les informations de date et d’heure de tous nos anciens messages vBulletin 4 à cause de cela. S’il s’agit d’une correction, j’aimerais savoir si une réimportation serait une bonne idée si tous les messages ont été transférés.

2 « J'aime »

Oui, le script générerait une erreur car il transmettait un entier à une fonction de temps.

3 « J'aime »

Non. Le script ignore les messages qui ont déjà été importés.

3 « J'aime »

Salut,

As-tu trouvé comment résoudre ce problème ?

Nos deux forums principaux/inférieurs ont un parentid = -1 (je pense que c’est dû à notre conversion depuis la v3 à l’époque).

Je ne suis pas sûr de la marche à suivre, dois-je simplement les définir à 0 si c’est -1 dans le script de conversion ? En supposant que 0 est la catégorie principale de Discourse ?

En fait, en regardant le site de Discourse maintenant ; ces deux-là semblent être les seuls qui ont été importés ?

 importing top level categories...
         2 / 2 (100.0%)  [211 items/min]  in]
 importing children categories...
 Traceback (most recent call last):
         5: from script/import_scripts/vbulletin.rb:1003:in `<main>'
         4: from /var/www/discourse/script/import_scripts/base.rb:47:in `perform'
         3: from script/import_scripts/vbulletin.rb:84:in `execute'
         2: from script/import_scripts/vbulletin.rb:287:in `import_categories'
         1: from script/import_scripts/vbulletin.rb:287:in `each'
script/import_scripts/vbulletin.rb:289:in `block in import_categories': undefined method `[]' for nil:NilClass (NoMethodError)
1 « J'aime »

Probablement. J’ai fait un tas d’importations vBulletin depuis lors. :person_shrugging:

Il vous suffit d’essayer et de voir ce qui se passe. Cela ressemble à la même chose que j’ai décrite.

Je modifierais simplement le script pour . . . faire quelque chose . . . si cette chose est nulle.

1 « J'aime »

Absolument, mais je n’en sais pas assez sur le fonctionnement de Discourse pour savoir à quoi le régler.
Que ferait Discourse si je les réglai à un nombre aléatoire comme 0 ? Ou devrais-je trouver un numéro de catégorie déjà dans la base de données et le régler à celui-ci ?

Pas très solide en Ruby, est-ce que cela fonctionnerait, selon vous ?

        if categories.detect { |c| c["forumid"] == cc["parentid"] }["parentid"].nil?
          cc["parentid"] = 52
        else
          cc["parentid"] = categories.detect { |c| c["forumid"] == cc["parentid"] }["parentid"]
        end

En fait, il semble qu’il y ait beaucoup de forums supprimés dont le parentid n’existe plus.

EDIT
Je viens de tout régler à un sujet parent, et je pourrai le corriger plus tard.

1 « J'aime »

Nous arrivons enfin à la partie d’importation des pièces jointes, elle était à environ 1,9 % et maintenant nous obtenons cette erreur

    67406 / 3550728 (  1.9%)  Traceback (most recent call last):
        23: from script/import_scripts/vbulletin.rb:1006:in `<main>'
        22: from /var/www/discourse/script/import_scripts/base.rb:47:in `perform'
        21: from script/import_scripts/vbulletin.rb:88:in `execute'
        20: from script/import_scripts/vbulletin.rb:610:in `import_attachments'
        19: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/querying.rb:22:in `find_each'
        18: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:70:in `find_each'
        17: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:137:in `find_in_batches'
        16: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:229:in `in_batches'
        15: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:229:in `loop'
        14: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:245:in `block in in_batches'
        13: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:138:in `block in find_in_batches'
        12: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:71:in `block in find_each'
        11: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:71:in `each'
        10: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/relation/batches.rb:71:in `block (2 levels) in find_each'
         9: from script/import_scripts/vbulletin.rb:651:in `block in import_attachments'
         8: from script/import_scripts/vbulletin.rb:651:in `each'
         7: from script/import_scripts/vbulletin.rb:659:in `block (2 levels) in import_attachments'
         6: from /var/www/discourse/script/import_scripts/base.rb:873:in `html_for_upload'
         5: from /var/www/discourse/script/import_scripts/base/uploader.rb:40:in `html_for_upload'
         4: from /var/www/discourse/lib/upload_markdown.rb:10:in `to_markdown'
         3: from /var/www/discourse/lib/upload_markdown.rb:19:in `image_markdown'
         2: from /var/www/discourse/app/models/upload.rb:206:in `short_url'
         1: from /var/www/discourse/app/models/upload.rb:534:in `short_url_basename'
/var/www/discourse/app/models/upload.rb:270:in `base62_sha1': undefined method `hex' for nil:NilClass (NoMethodError)

undefined method `hex’ for nil:NilClass (NoMethodError)

Quelqu’un a une idée sur la façon de résoudre ce problème ?

Essaie-t-il de lire short_url_basename, et il renvoie nil ; donc .hex échoue ?

1 « J'aime »

Ma supposition, sans regarder le code, est que le fichier est manquant ou qu’il y a peut-être un champ filename et qu’il est vide ? Je mettrais probablement un puts dans import_attachments et je verrais ce qu’il y a dans l’enregistrement qu’il essaie d’importer.

1 « J'aime »

Merci pour votre aide ! Je suis nouveau dans Ruby, serait-ce la bonne façon de faire ?

      unless mapping[post.id].nil? || mapping[post.id].empty?
        mapping[post.id].each do |attachment_id|
          upload, filename = find_upload(post, attachment_id)
          unless upload
            fail_count += 1
            next
          end

          puts "{short_url_basename}"

          # la déduplication interne des téléchargements garantira que nous n'importerons pas à nouveau les pièces jointes
          html = html_for_upload(upload, filename)
          if !new_raw[html]
            new_raw += "\n\n#{html}\n\n"
          end
        end
      end

Aha, short_url_basename est une fonction, donc ça ne fonctionnera pas.

Est-ce simplement, puts “{post}” ? Et cela affichera tout le contenu de l’objet post ?

Cela semble être la ligne qui plante dans upload.rb

upload_markdown 19
"![#{@upload.original_filename}|#{@upload.width}x#{@upload.height}](#{@upload.short_url})"

upload.rb 534
"#{Upload.base62_sha1(sha1)}#{extension.present? ? ".#{extension}" : ""}"

upload.rb 270
Base62.encode(sha1.hex)

Donc, c’est soit upload.original_filename, upload.width, upload.height ou upload.short_url alors

Donc, si je fais une vérification nil dans upload_markdown, cela devrait empêcher l’erreur, n’est-ce pas ?

A-t-il besoin du shortURL pour fonctionner ; pourrais-je simplement créer mon propre shortURL aléatoire ?

2 « J'aime »

Je pense que c’est là que se situe le problème. Il ne trouve pas le téléchargement, donc il renvoie nil. Peut-être que le fichier est manquant ou invalide.

1 « J'aime »

Mais cela ne le détecterait-il pas alors ?

unless upload
  fail_count += 1
  next
end

Ou bien unless ne vérifie-t-il pas nil ?

Ou bien cela passe-t-il parce qu’il a créé l’objet upload, mais que la propriété upload.short_url dans l’objet upload est manquante, peut-être ?

1 « J'aime »

Désolé. C’est exact. Ça le détecterait. Je crains que ce niveau de débogage ne soit pas vraiment approprié pour un forum. :person_shrugging:
Vous êtes sur la bonne voie, cependant. Continuez comme ça. Il semble que vous en sachiez assez pour y arriver. J’ai écrit au moins quelques importateurs avant d’apprendre le Ruby.

1 « J'aime »