Ein Migration eines NodeBB-Forums mit MongoDB zu Discourse

Wie Sie richtig wissen, unterstützt NodeBB zwei Datenbank-Backends: Redis und MongoDB. Das Discourse-Importskript unterstützt beide. In diesem Tutorial lernen wir, wie wir NodeBB mit MongoDB als Datenbank-Backend migrieren. Wir verwenden den NodeBB-Importer mit dem mongo-Adapter. Wenn Ihr NodeBB-Forum Redis als Backend verwendet, folgen Sie bitte diesem Tutorial, das den redis-Adapter demonstriert.

Der Plan

  • Vorbereitung der Entwicklungsumgebung.
  • Export der Datenbank aus der Produktionsumgebung.
  • Import der Produktionsdatenbank in eine Discourse-Instanz.
  • Ausführung des Importskripts.

Was migriert werden kann

  • Gruppen
  • Anhänge
  • Kategorien
    • Root-Kategorie => Root-Kategorie
    • Unterkategorie & Unter-Unterkategorie => Unterkategorie
  • Themen & Beiträge
    • angepinntes Thema => angepinntes Thema
    • gesperrtes Thema => geschlossenes Thema
    • Themenaufrufe
    • upvoted_by
    • Stile, Erwähnungen, Emojis und Anhänge.
  • Benutzer (mit folgenden Attributen)
    • Profilhintergrund
    • Avatare
    • Sperrstatus
    • Benutzername
    • Name
    • E-Mail
    • Administrator
    • Biografie
    • Gruppe
    • Website
    • Standort
    • Beitrittsdatum

Vorbereitung der lokalen Entwicklungsumgebung

Wie in unserem Plan erwähnt, müssen wir zunächst unsere Entwicklungsumgebung vorbereiten. Folgen Sie einer dieser Anleitungen, um Discourse selbst zu installieren:

:bulb: Bitte konsultieren Sie diese Anleitung, falls Sie Probleme beim Einrichten des Discourse-Servers haben.

Installieren Sie anschließend den MongoDB-Datenbankserver.

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

Für weitere Details verweisen wir auf den offiziellen Leitfaden.

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

Für weitere Details verweisen wir auf den offiziellen Leitfaden.

Windows 10:

Laden Sie das Installationsprogramm herunter und installieren Sie MongoDB als Windows-Dienst:

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

Führen Sie die cmd mit Administratorrechten aus, um zu überprüfen, ob der MongoDB-Server ordnungsgemäß funktioniert:

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

Für weitere Details verweisen wir auf den offiziellen Leitfaden.

Diese Umgebung wird unser Discourse-Server sein.

Exportieren des Produktionsdatenbank-Dumps:

Fahren Sie Ihr NodeBB-Forum (Produktionsserver) herunter.

$ cd /path_to_nodebb
$ ./nodebb stop

Fahren Sie Ihre Datenbank herunter:

$ sudo service mongodb stop

Sichern Sie Ihre Datenbank:

$ mongodump --out ~/my_backup_path/

Die Ausgabe des vorherigen Befehls sieht ungefähr so aus:

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)

Beachten Sie, dass das Backup tatsächlich ein Verzeichnis und nicht nur eine Datei ist.

:bulb: Sie können die Größe Ihrer Datenbank überprüfen, indem Sie use myDatabase und dann db.stats().dataSize; innerhalb der mongo-CLI ausführen. Der zurückgegebene Wert ist in Bytes.

Sichern Sie Ihre Forum-Assets:

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

Nachdem Sie Ihre Datenbank und Forum-Assets gesichert haben, sollten Sie diese auf den Discourse-Server kopieren.

Importieren der Datenbank

Da wir nun unsere Datenbank haben, können wir sie in unsere lokale MongoDB-Instanz importieren:

$ mongorestore ~/path_of_my_backup_directory/

Für weitere Optionen verweisen wir auf den offiziellen Leitfaden.

Als Nächstes müssen Sie die uploads.tar.gz entpacken, damit der Importeur die Assets importieren kann:

$ tar xvzf uploads.tar.gz

Ausführen des Importskripts

Da unsere Datenbank und Assets jetzt vorhanden sind, sind wir bereit, unser Importskript auszuführen. Zuvor müssen wir das NodeBB-Importskript bearbeiten, um es an unsere Bedürfnisse anzupassen.

Dies ist der Pfad zu Ihrem NodeBB-Upload-Ordner:

ATTACHMENT_DIR = '/absolute_path/uploads'

Als Nächstes müssen wir dem Importeur mitteilen, dass er den mongo-Adapter anstelle des redis-Adapters verwenden soll:

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
# )

Führen Sie den Importeur mit einem sauberen Discourse und Unterstützung für das mongo-Gem aus:

$ 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

Der Importeur verbindet sich mit der MongoDB-Instanz und migriert alles nach Discourse.

Nachdem der Importeur abgeschlossen ist, starten Sie Discourse:

$ bundle exec rails server

Starten Sie Sidekiq, um die migrierten Daten zu verarbeiten:

$ bundle exec sidekiq

Sie können den Fortschritt unter http://localhost:3000/sidekiq/queues überwachen.

Führen Sie ein Discourse-Backup durch und laden Sie es auf Ihren Discourse-Produktionsserver hoch, indem Sie diesem Tutorial folgen.

:tada:

Wenn Sie Fragen zum Prozess haben, helfe ich gerne weiter.

Viel Erfolg bei der Migration :grinning:

10 „Gefällt mir“

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 „Gefällt mir“

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 „Gefällt mir“

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'

Hey,

Thanks for the guide! When I run the migration script, this is what I get when the script reaches the point where it imports users:

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)

I managed to bypass this by commenting out the part that uploads profile pictures, that’s not a problem.

Now I get the nil error when importing topics. :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)

Falls sich jemand für die Zukunft interessiert: Ich habe die Migration erfolgreich durchgeführt. Hier sind einige Anmerkungen zu meinen Erfahrungen; dabei gibt es ein paar Fallstricke.

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

2 „Gefällt mir“
21: from script/import_scripts/nodebb/nodebb.rb:532:in `\u003cmain\u003e'
        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 Ich habe den gleichen Fehler wie @OdysLam @FreeWorLD.

Hallo zusammen!
Hat schon einmal jemand versucht, von der neuesten NodeBB 4.9.x auf die aktuelle Discourse-Entwicklungsumgebung zu migrieren?

/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>'

Ich erhalte diesen Fehler während der Migration in der Entwicklungsumgebung.
Irgendwelche Ideen?

Werden Migrationsskripte von der Community im Jahr 2026 noch unterstützt?
Das Feld timestamp existiert und enthält gültige Daten

Danke.

[quote=“Twissell, post:17, topic:126553”]'NodeBB::Mongo#post': undefined method '[]' for nil

  post["timestamp"] = timestamp_to_date(post["timestamp"])

[/quote]

Das Problem ist, dass post nil ist. Sie haben die Dinge also nicht richtig konfiguriert. Wurden Benutzer importiert?

Wir können es versuchen, aber es ist ziemlich begrenzt. Wahrscheinlich müssen Sie verstehen, wie eine Reihe von Dingen funktionieren, die über das hinausgehen, was in einem Forum leicht unterstützt werden kann.

3 „Gefällt mir“
Benutzer werden importiert
      182 / 182 (100,0 %)  [437953 Elemente/Min]
Benutzer zu Gruppen hinzufügen...

Themen werden importiert...
      132 / 132 (100,0 %)  [3567036 Elemente/Min]
Beiträge werden importiert...
/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>'

Benutzer scheinen laut Skriptausgabe korrekt importiert worden zu sein, siehe oben.

Entschuldigung, sind Sie ein lebender Mensch oder nur ein kluger KI-Assistent?

Dann liegt das Problem darin, dass die Beitragsdaten nicht gelesen werden.

Ich vermute, es liegt hier und aus irgendeinem Grund findet es die Beiträge nicht.

    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

Ich bin ein lebender Mensch, der seit einem Jahrzehnt seinen Lebensunterhalt mit der Unterstützung von Discourse verdient und eine Reihe von Importern geschrieben und über hundert Foren von verschiedenen anderen Plattformen importiert hat.

Wenn Sie Hilfe von einem KI-Assistenten wünschen, besuchen Sie https://ask.discourse.com/

6 „Gefällt mir“

Irgendwie habe ich es geschafft, dass die Skriptfunktion richtig funktioniert.

Hier sind meine Änderungen (mit ein wenig Hilfe von einem 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  # <-- verwirft alle nil-Ergebnisse (verwaiste pids)
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- Null-Prüfung
    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|
        # überspringe, wenn post null ist
		# überspringe, wenn es merged_post ist
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # überspringe, wenn es gelöscht ist
        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	

Es scheint jetzt auf die richtige Weise zu funktionieren.

Obwohl ich nicht weiß, wie korrekt dies aus der Sicht der internen Architektur von Discourse ist, scheint es auf den ersten Blick zu funktionieren.

Jeder Vorschlag zur Verbesserung und Optimierung ist herzlich willkommen.

1 „Gefällt mir“

Tipp: Lass es nicht komplett stillschweigend fehlschlagen – ändere dies in

if post.nil?
  puts "!!! Could not find post #{id}"
  return nil
end

Andernfalls wirst du dich fragen, warum die Hälfte deiner Beiträge fehlt, und nach stundenlanger Frustration stellst du fest, dass es daran liegt.

Dasselbe gilt für diese beiden

        next if post.nil?
        next if post["deleted"] == "1"
5 „Gefällt mir“

Methode posts in der Datei mongo.rb neu geschrieben

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

Fügen Sie hier den Zitattext ein

Dieser Ansatz stellt sicher, dass Beiträge innerhalb eines Themas in der korrekten chronologischen Reihenfolge sortiert sind. Aufsteigend.
Beachten Sie den Aufruf sort(score: 1).