Migrer un forum NodeBB avec MongoDB vers Discourse

Comme vous le savez, NodeBB prend en charge deux backends de base de données : Redis et MongoDB. Le script d’importation Discourse les prend tous deux en charge. Dans ce tutoriel, nous apprendrons comment migrer NodeBB avec MongoDB comme backend de base de données. Nous utiliserons NodeBB Importer avec l’adaptateur mongo. Si votre forum NodeBB utilise Redis comme backend, veuillez suivre ce tutoriel qui démontre l’adaptateur redis.

Le plan

  • Préparation de l’environnement de développement.
  • Export de la base de données depuis l’environnement de production.
  • Importation de la base de données de production dans une instance Discourse.
  • Exécution du script d’importation.

Ce qui peut être migré

  • Groupes
  • Pièces jointes
  • Catégories
    • Catégorie racine => Catégorie racine
    • Sous-catégorie et sous-sous-catégorie => Sous-catégorie
  • Sujets et messages
    • sujet épinglé => sujet épinglé
    • sujet verrouillé => sujet fermé
    • vues du sujet
    • upvoted_by
    • styles, mentions, emojis et pièces jointes.
  • Utilisateurs (avec les attributs suivants)
    • arrière-plan du profil
    • avatars
    • statut banni
    • nom d’utilisateur
    • nom
    • adresse e-mail
    • administrateur
    • biographie
    • groupe
    • site web
    • lieu
    • date d’adhésion

Préparation de l’environnement de développement local

Comme mentionné dans notre plan, nous devons d’abord préparer notre environnement de développement. Suivez l’un de ces guides pour installer Discourse lui-même :

:bulb: Veuillez consulter ce guide si vous rencontrez des problèmes lors de la configuration du serveur Discourse.

Ensuite, installez le serveur de base de données MongoDB.

Ubuntu-18-04 :

$ wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -
$ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
$ sudo apt-get update
$ sudo apt-get install -y mongodb-org
$ sudo service mongod status

Pour plus de détails, référez-vous au guide officiel.

Mac OS :

$ brew tap mongodb/brew
$ brew install mongodb-community@4.0
$ brew services start mongodb-community@4.0
$ brew services status mongodb-community@4.0

Pour plus de détails, référez-vous au guide officiel.

Windows 10 :

Téléchargez le programme d’installation et installez MongoDB en tant que service Windows :

https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.12-signed.msi

Exécutez cmd avec des privilèges d’administrateur pour vérifier si le serveur mongo fonctionne correctement :

"C:\Program Files\MongoDB\Server\4.0\bin\mongo.exe"

Pour plus de détails, référez-vous au guide officiel.

Cet environnement sera notre serveur Discourse.

Exportation du dump de la base de données de production :

Arrêtez votre forum NodeBB (serveur de production).

$ cd /chemin_vers_nodebb
$ ./nodebb stop

Arrêtez votre base de données :

$ sudo service mongodb stop

Sauvegardez votre base de données :

$ mongodump --out ~/mon_chemin_de_sauvegarde/

La sortie de la commande précédente ressemblera à ceci :

2019-08-16T15:17:09.845+0300 done dumping admin.system.users (1 document)
2019-08-16T15:17:09.846+0300 done dumping admin.system.version (2 documents)
2019-08-16T15:17:09.849+0300 done dumping nodebb.sessions (10 documents)
2019-08-16T15:17:09.850+0300 done dumping nodebb.socket.io (215 documents)
2019-08-16T15:17:09.854+0300 done dumping nodebb.objects (1488 documents)

Notez que la sauvegarde est en réalité un répertoire, et non simplement un fichier.

:bulb: Vous pouvez vérifier la taille de votre base de données en exécutant use maBaseDeDonnees puis db.stats().dataSize; dans l’interface de ligne de commande mongo. La valeur retournée sera en octets.

Sauvegardez les ressources de votre forum :

$ cd /chemin_vers_racine_nodebb/
$ tar -czf ./uploads.tar.gz ./public/uploads

Une fois que vous avez votre base de données et les ressources du forum, vous devez les copier vers le serveur Discourse.

Importation de la base de données

Maintenant que nous avons notre base de données, nous pouvons l’importer dans notre instance MongoDB locale :

$ mongorestore ~/chemin_du_repertoire_de_sauvegarde/

Pour plus d’options, référez-vous au guide officiel.

Ensuite, vous devez extraire uploads.tar.gz afin que l’importateur puisse importer les ressources :

$ tar xvzf uploads.tar.gz

Exécution du script d’importation

Maintenant que notre base de données et nos ressources sont en place, nous sommes prêts à exécuter notre script d’importation. Avant cela, nous devons modifier le script d’importation NodeBB pour qu’il réponde à nos besoins.

Voici le chemin de votre dossier d’uploads NodeBB :

ATTACHMENT_DIR = '/chemin_absolu/uploads'

Ensuite, nous devons indiquer à l’importateur d’utiliser l’adaptateur mongo au lieu de l’adaptateur redis :

adapter = NodeBB::Mongo
@client = adapter.new('mongodb://127.0.0.1:27017/nodebb')

# adapter = NodeBB::Redis
# @client = adapter.new(
# host: "localhost",
# port: "6379",
# db: 14
# )

Exécutez l’importateur avec Discourse propre et le support du gem mongo :

$ cd ~/discourse
$ echo "gem 'mongo'" >> Gemfile
$ bundle install
$ bundle exec rake db:drop db:create db:migrate
$ bundle exec ruby script/import_scripts/nodebb/nodebb.rb

L’importateur se connectera à l’instance MongoDB et migrera tout vers Discourse.

Une fois l’importation terminée, démarrez Discourse :

$ bundle exec rails server

Démarrez Sidekiq pour traiter les données migrées :

$ bundle exec sidekiq

Vous pouvez surveiller la progression à l’adresse http://localhost:3000/sidekiq/queues.

Effectuez une sauvegarde Discourse et téléversez-la sur votre serveur de production Discourse en suivant ce tutoriel.

:tada:

Si vous avez des questions sur le processus, je suis heureux de vous aider.

Bonne migration :grinning:

10 « J'aime »

Great work really. While I was trying, I am getting below error. Any idea what I might be doing wrong here?

importing groups
   10 / 10 (100.0%)  [474765 items/min]    
importing top level categories...
    8 / 8 (100.0%)  [437896 items/min]    
importing child categories...
   68 / 68 (100.0%)  [774048 items/min]  
importing users
 5534 / 5534 (100.0%)  [355520 items/min]    
adding users to groups...

importing topics...
Traceback (most recent call last):
	12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
	11: from /home/fsuzer/discourse/script/import_scripts/base.rb:47:in `perform'
	10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
	 9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
	 8: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `batches'
	 7: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `loop'
	 6: from /home/fsuzer/discourse/script/import_scripts/base.rb:878:in `block in batches'
	 5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
	 4: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
	 3: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
	 2: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
	 1: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

Some field from the topic is empty. Or perhaps the topic query returned nothing.

1 « J'aime »

Can someone help me?

Error: Could not find gem ‘mongo’ in any of the gem sources listed in your Gemfile.

After $ bundle install

This will happen if you skipped this:

Make sure you Gemfile has this line:

gem 'mongo'

Then run:

$ bundle install
1 « J'aime »

Gem

Thanks for your help. But this line already exists in my file. I also did the previous steps informed.

I don’t know if that helps, but when I run gem list mongo it’s not listed.

Your Gemfile sounds weird to me. Please, can you share the whole content?

Your Gemfile is just wrong. It should have another content, just like this:

In addition to:

gem 'mongo'

Salut,

Merci pour le guide ! Lorsque j’exécute le script de migration, voici ce que j’obtiens lorsque le script atteint l’étape d’importation des utilisateurs :

Traceback (most recent call last):
        21: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        20: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute'
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users'
        17: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `create_users'
        16: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `each'
        15: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:274:in `block in create_users'
        14: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:391:in `create_user'
        13: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `try'
        12: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `public_send'
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users'
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture'
         9: from /home/odyslam/forum/discourse/lib/upload_creator.rb:45:in `create_for'
         8: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:14:in `synchronize'
         7: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         6: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         5: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:33:in `block in synchronize'
         4: from /home/odyslam/forum/discourse/lib/upload_creator.rb:66:in `block in create_for'
         3: from /home/odyslam/forum/discourse/lib/upload_creator.rb:381:in `optimize!'
         2: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `ensure_safe_paths!'
         1: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `each'
/home/odyslam/forum/discourse/app/models/optimized_image.rb:179:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

J’ai réussi à contourner cela en commentant la partie qui télécharge les photos de profil, ce n’est pas un problème.

Maintenant, j’obtiens l’erreur nil lors de l’importation des sujets. :frowning:

Traceback (most recent call last):
        12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        11: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
         9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
         8: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `batches'
         7: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `loop'
         6: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:863:in `block in batches'
         5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
         4: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
         3: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
         2: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
         1: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

Au cas où quelqu’un serait intéressé pour l’avenir, j’ai réussi à effectuer la migration. Voici quelques commentaires concernant mon expérience ; il y a quelques pièges dans le processus.

https://odyslam.com/blog/Migrating-from-Nodebb-to-Discourse/

2 « J'aime »
21: from script/import_scripts/nodebb/nodebb.rb:532:in `main`
        20: from /home/nodebb/discourse/script/import_scripts/base.rb:47:in `perform`
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute`
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users`
        17: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `create_users`
        16: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `each`
        15: from /home/nodebb/discourse/script/import_scripts/base.rb:276:in `block in create_users`
        14: from /home/nodebb/discourse/script/import_scripts/base.rb:393:in `create_user`
        13: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `try`
        12: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `public_send`
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users`
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture`
         9: from /home/nodebb/discourse/lib/upload_creator.rb:64:in `create_for`
         8: from /home/nodebb/discourse/lib/distributed_mutex.rb:14:in `synchronize`
         7: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize`
         6: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize`
         5: from /home/nodebb/discourse/lib/distributed_mutex.rb:33:in `block in synchronize`
         4: from /home/nodebb/discourse/lib/upload_creator.rb:83:in `block in create_for`
         3: from /home/nodebb/discourse/lib/upload_creator.rb:501:in `optimize!`
         2: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `ensure_safe_paths!`
         1: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `each`
/home/nodebb/discourse/app/models/optimized_image.rb:182:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

@enigmaty Je rencontre la même erreur que @OdysLam @FreeWorLD.

Bonjour !
Quelqu’un a-t-il déjà essayé de migrer de la dernière version de NodeBB 4.9.x vers la version de développement actuelle de Discourse ?

/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from 内部:kernel:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'

J’ai cette erreur lors de la migration dans l’environnement de développement.
Des idées ?

Les scripts de migration sont-ils toujours supportés par la Communauté en 2026 ?
Le champ timestamp existe et contient des données valides.

Merci.

Le problème est que post est nil. Vous n’avez donc pas configuré les choses correctement. Avez-vous importé les utilisateurs ?

Nous pouvons essayer, mais c’est assez limité. Vous devrez probablement comprendre comment fonctionnent beaucoup de choses qui peuvent dépasser ce qui est facilement supportable dans un forum.

3 « J'aime »
importing users
      182 / 182 (100.0%)  [437953 items/min]
adding users to groups...

importing topics...
      132 / 132 (100.0%)  [3567036 items/min]
importing posts...
/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from 内部:kernel:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'


Les utilisateurs semblent avoir été importés correctement selon la sortie du script, voir ci-dessus.

Désolé, êtes-vous une personne vivante ou juste une IA-assistante suffisamment intelligente ?

Alors le problème vient du fait qu’il ne lit pas les données des publications.

Mon hypothèse est que cela se situe ici et que pour une raison quelconque, il ne trouve pas les publications.

    def posts(offset = 0, page_size = 2000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)

      post_keys.map { |post_key| post(post_key) }
    end

Je suis une personne réelle qui gagne sa vie en soutenant Discourse depuis une décennie et qui a écrit un tas d’importateurs et a importé plus d’une centaine de forums à partir de diverses autres plateformes.

Si vous voulez l’aide d’un assistant IA, consultez https://ask.discourse.com/

6 « J'aime »

J’ai réussi d’une manière ou d’une autre à faire fonctionner la fonction du script correctement.

Voici mes changements (avec un petit coup de main de Claude Code :slight_smile:)

 =>mongo.rb
   
   def posts(offset = 0, page_size = 1000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)
      post_keys
          .map { |pid| post(pid) }
          .compact  # <-- supprime tous les résultats nuls (pids orphelins)
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- vérification nulle
    post["timestamp"] = timestamp_to_date(post["timestamp"])
    if post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first
        post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first[:members]
      else
        post["upvoted_by"] = []
      end

      post["pid"] = post["pid"].to_s
      post["deleted"] = post["deleted"].to_s

      post
    end
	
=>nodebb.rb

 create_posts(posts, total: post_count, offset: offset) do |post|
        # sauter si le post est nul
		# sauter si c'est un merged_post
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # sauter s'il est supprimé
        next if post["deleted"] == "1"

        raw = post["content"]
        post_id = "p#{post["pid"]}"

        next if raw.blank?
        topic = topic_lookup_from_imported_post_id("t#{post["tid"]}")

        unless topic
          puts "Topic with id #{post["tid"]} not found, skipping"
          next
        end	

Il semble fonctionner correctement maintenant.

Bien que je ne sache pas à quel point c’est correct du point de vue de l’architecture interne de Discourse, à première vue, cela semble fonctionner.

Toutes suggestions d’amélioration et d’optimisation sont les bienvenues.

1 « J'aime »

Astuce : ne faites pas en sorte que cela échoue complètement en silence - changez ceci en

if post.nil?
  puts "!!! Impossible de trouver le message #{id}"
  return nil
end

Sinon, vous vous demanderez pourquoi la moitié de vos messages manque et après des heures de frustration, vous découvrirez que c’est à cause de cela.

même chose pour ces deux-là

        next if post.nil?
        next if post["deleted"] == "1"
5 « J'aime »

Méthode posts réécrite dans le fichier mongo.rb

def posts(offset = 0, page_size = 1000)
post_keys = mongo.find(_key: “posts:pid”).sort(score: 1).skip(offset).limit(page_size).pluck(:value)
post_keys.map { |pid| post(pid)}.compact
end

Insérez le texte de la citation ici

Cette approche garantit que les publications au sein d’un sujet sont triées dans le bon ordre chronologique. Ascendant.
Notez l’appel sort(score: 1).