Migrare un forum NodeBB con MongoDB a Discourse

Come ben sapete, NodeBB supporta due backend di database: Redis e MongoDB. Lo script di importazione di Discourse supporta entrambi. In questo tutorial impareremo come migrare NodeBB utilizzando MongoDB come backend del database. Useremo NodeBB Importer con l’adattatore mongo. Se il tuo forum NodeBB utilizza Redis come backend, segui invece questo tutorial che illustra l’adattatore redis.

Il piano

  • Preparazione dell’ambiente di sviluppo.
  • Esportazione del database dall’ambiente di produzione.
  • Importazione del database di produzione in un’istanza di Discourse.
  • Esecuzione dello script di importazione.

Cosa può essere migrato

  • Gruppi
  • Allegati
  • Categorie
    • Categoria radice => Categoria radice
    • Sottocategoria e sottocategoria di secondo livello => Sottocategoria
  • Argomenti e post
    • argomento fissato => argomento fissato
    • argomento bloccato => argomento chiuso
    • visualizzazioni degli argomenti
    • upvoted_by
    • stili, menzioni, emoji e allegati.
  • Utenti (con i seguenti attributi)
    • sfondo del profilo
    • avatar
    • stato di sospensione
    • nome utente
    • nome
    • email
    • amministratore
    • biografia
    • gruppo
    • sito web
    • località
    • data di iscrizione

Preparazione dell’ambiente di sviluppo locale

Come menzionato nel nostro piano, dobbiamo prima preparare il nostro ambiente di sviluppo. Segui una di queste guide per installare Discourse:

:bulb: Ti invitiamo a consultare questa guida se incontri problemi nella configurazione del server Discourse.

Successivamente, installa il server di database 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

Per ulteriori dettagli, consulta la guida ufficiale.

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

Per ulteriori dettagli, consulta la guida ufficiale.

Windows 10:

Scarica il programma di installazione e installa MongoDB come servizio Windows:

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

Esegui cmd con privilegi di amministratore per verificare che il server mongo funzioni correttamente:

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

Per ulteriori dettagli, consulta la guida ufficiale.

Questo ambiente sarà il nostro server Discourse.

Esportazione del dump del database di produzione

Arresta il tuo forum NodeBB (server di produzione).

$ cd /path_to_nodebb
$ ./nodebb stop

Arresta il tuo database:

$ sudo service mongodb stop

Esegui il backup del tuo DB:

$ mongodump --out ~/my_backup_path/

L’output del comando precedente sarà simile a questo:

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)

Tieni presente che il backup è effettivamente una directory, non solo un file.

:bulb: Puoi verificare la dimensione del tuo DB eseguendo use myDatabase e poi db.stats().dataSize; all’interno della CLI mongo. Il valore restituito sarà in byte.

Esegui il backup degli asset del forum:

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

Dopo aver ottenuto il DB e gli asset del forum, copiali sul server Discourse.

Importazione del database

Ora che abbiamo il nostro database, possiamo importarlo nella nostra istanza locale di MongoDB:

$ mongorestore ~/path_of_my_backup_directory/

Per ulteriori opzioni, consulta la guida ufficiale.

Successivamente, devi estrarre uploads.tar.gz in modo che l’importatore possa importare gli asset:

$ tar xvzf uploads.tar.gz

Esecuzione dello script di importazione

Ora che il database e gli asset sono pronti, siamo pronti per eseguire lo script di importazione. Prima di ciò, dobbiamo modificare lo script di importazione di NodeBB per adattarlo alle nostre esigenze.

Questo è il percorso della cartella uploads di NodeBB:

ATTACHMENT_DIR = '/absolute_path/uploads'

Successivamente, dobbiamo indicare all’importatore di utilizzare l’adattatore mongo invece di 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
# )

Esegui l’importatore con Discourse pulito e supporto per il 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’importatore si connetterà all’istanza MongoDB e migrerà tutto su Discourse.

Dopo il completamento dell’importazione, avvia Discourse:

$ bundle exec rails server

Avvia Sidekiq per elaborare i dati migrati:

$ bundle exec sidekiq

Puoi monitorare l’avanzamento su http://localhost:3000/sidekiq/queues.

Esegui un backup di Discourse e caricalo sul tuo server di produzione Discourse seguendo questo tutorial.

:tada:

Se hai domande sul processo, sono felice di aiutarti.

Buona migrazione :grinning:

10 Mi Piace

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 Mi Piace

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 Mi Piace

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'

Ciao,

Grazie per la guida! Quando eseguo lo script di migrazione, ecco cosa ottengo quando lo script raggiunge il punto in cui importa gli utenti:

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)

Sono riuscito a aggirare il problema commentando la parte che carica le foto del profilo; quello non è un problema.

Ora ricevo l’errore nil durante l’importazione dei topic. :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)

Nel caso qualcuno fosse interessato in futuro, sono riuscito a eseguire la migrazione. Ecco alcuni commenti sulla mia esperienza; ci sono un paio di insidie nel processo.

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

2 Mi Piace
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 Sto riscontrando lo stesso errore di @OdysLam @FreeWorLD.

Ciao a tutti!
Qualcuno ha mai provato a migrare dall’ultima versione di NodeBB 4.9.x all’attuale sviluppo di 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 <internal: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>'

Ho riscontrato questo errore durante la migrazione nell’ambiente di sviluppo.
Qualche idea?

Gli script di migrazione sono ancora supportati dalla Community nel 2026?
Il campo timestamp esiste e contiene dati validi

Grazie.

Il problema è che post è nil. Quindi non hai configurato le cose correttamente. Ha importato gli utenti?

Possiamo provare, ma è piuttosto limitato. È probabile che tu debba capire come funzionano molte cose che possono andare oltre ciò che è facilmente supportabile in un forum.

3 Mi Piace
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 
        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>'


Gli utenti sembrano essere stati importati correttamente in base all’output dello script, vedi sopra.

Scusa, sei una persona reale o solo un assistente AI abbastanza intelligente?

Allora il problema è che non sta leggendo i dati dei post.

La mia ipotesi è che sia qui e per qualche motivo non trovi i post.

    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

Sono una persona reale che ha guadagnato da vivere supportando Discourse per un decennio e che ha scritto un bel po’ di importatori e ha importato oltre cento forum da varie altre piattaforme.

Se vuoi aiuto da un assistente AI, consulta https://ask.discourse.com/

6 Mi Piace

Sono riuscito in qualche modo a far funzionare correttamente la funzione dello script.

Ecco le mie modifiche (con un piccolo aiuto da un 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  # <-- scarta qualsiasi risultato nil (pid orfani)
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- controllo null
    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|
        # salta se il post è nullo
		# salta se è un merged_post
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # salta se è cancellato
        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	

Sembra che ora funzioni nel modo giusto.

Anche se non so quanto sia corretto dal punto di vista dell’architettura interna di Discourse, a prima vista sembra funzionare.

Qualsiasi suggerimento per miglioramenti e ottimizzazioni sono benvenuti.

1 Mi Piace

Suggerimento: non farlo fallire completamente in silenzio - cambialo in

if post.nil?
  puts "!!! Impossibile trovare il post #{id}"
  return nil
end

Altrimenti ti ritroverai a chiederti perché metà dei tuoi post mancano e dopo ore di frustrazione scoprirai che è dovuto a questo.

Stessa cosa per questi due

        next if post.nil?
        next if post["deleted"] == "1"
5 Mi Piace

Metodo posts riscritto nel file 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

Inserisci qui il testo della citazione

Questo approccio assicura che i post all’interno di un argomento siano ordinati nel corretto ordine cronologico. Crescente.
Nota la chiamata sort(score: 1).