将带有MongoDB的NodeBB论坛迁移到Discourse

As you properly know, NodeBB supports two DB backends, Redis and MongoDB. Discourse importer script supports them both. In this tutorial, we will learn how to migrate NodeBB with MongoDB as DB backend. We will be using NodeBB Importer with mongo adapter. If your NodeBB forum uses Redis as backend then please follow this tutorial which demonstrate the redis adapter.

The plan

  • Preparing the development environment.

  • Export database from production environment.

  • Import the production DB to a Discourse instance.

  • Run the importer script.

What can be migrated

  • Groups
  • Attachments
  • Categories
    • Root Category => Root Category
    • Sub Category & Sub-Sub-Category => Sub-Category
  • Topics & Posts
    • pinned topic => pinned topic
    • locked topic => closed topic
    • topic views
    • upvoted_by
    • styles, mentions, emoji, and attachments.
  • Users (with the following attributes)
    • profile background
    • avatars
    • banned status
    • username
    • name
    • email
    • admin
    • bio
    • group
    • website
    • location
    • joining at

Preparing Local Development Environment

As mentioned in our plan we first need to prepare our development environment. Follow one of these guides to install Discourse itself:

:bulb: Please, consult this guide if you have any problems setting up Discourse server.

Then install MongoDB database server.

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

For more details, refer to the official guide

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

For more details, refer to the official guide

Windows 10:

Download the installer and install MongoDB as a Windows service:

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

Run the cmd with Administrative privileges to check if the mongo server is working properly:

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

For more details, refer to the official guide.

This environment will be our Discourse server.

Exporting Production Database Dump:

Shut down your NodeBB forum (production server).

$ cd /path_to_nodebb
$ ./nodebb stop

Shut down your database:

$ sudo service mongodb stop

Backup your DB:

$ mongodump --out ~/my_backup_path/

The output of the previous command will be something like this:

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)

Note that the backup is actually a directory, not just a file.

:bulb: You can check the size of your DB by running use myDatabase then db.stats().dataSize; inside mongo CLI. The returned value will be in bytes.

Backup your forum assets:

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

After you have your DB and assets forum you should copy them to Discourse server.

Importing The Database

Now that we have our database we can import it in our local MongoDB instance:

$ mongorestore ~/path_of_my_backup_directory/

For more options, refer to official guide.

Next, you need to extract the uploads.tar.gz so the importer can import the assets:

$ tar xvzf uploads.tar.gz

Running The Importer Script

Now that our database and assets in place we are ready to run our importer script. Before that, we need to edit the NodeBB importer script to fit our needs.

This is the path of your NodeBB uploads folder:

ATTACHMENT_DIR = '/absolute_path/uploads'

Next, we need to tell the importer to use the mongo adapter instead of redis adapter:

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

Run the importer with clean Discourse and mongo gem support:

$ 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

The importer will connect to the MongoDB instance and migrates everything to Discourse.

After the importer finishes, start Discourse:

$ bundle exec rails server

Start Sidekiq to process the migrated data:

$ bundle exec sidekiq

You can monitor the progress at http://localhost:3000/sidekiq/queues.

Perform a Discourse backup and upload it to your Discourse production server by following this tutorial.

:tada:

If you have any questions about the process I am happy to help.

Happy migration :grinning:

10 个赞

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 个赞

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 个赞

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)

In case anyone is interested in the future, I managed to perform the migration. Here are some comments regarding my experience; there are a couple of gotchas in the process.

2 个赞
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 我遇到了和 @OdysLam @FreeWorLD 一样的错误。

您好!

有人尝试过从最新的 NodeBB 4.9.x 迁移到当前的 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>'

我在开发环境中迁移时遇到了这个错误。
有什么想法吗?

迁移脚本在 2026 年是否仍然受社区支持?
字段 timestamp 存在并且包含有效数据

谢谢。

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

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

[/quote]

问题在于 post 是 nil。所以你的配置不正确。它导入用户了吗?

我们可以尝试一下,但这非常有限。你可能需要了解很多工作原理,这些内容可能超出了论坛容易支持的范围。

3 个赞
正在导入用户
      182 / 182 (100.0%)  [437953 items/min]
正在将用户添加到组...

正在导入主题...
      132 / 132 (100.0%)  [3567036 items/min]
正在导入帖子...
/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>'


根据脚本输出,用户似乎已正确导入,请参见上文。

抱歉,您是真人还是足够聪明的 AI 助手?

那么问题就出在它没有读取帖子数据上。

我猜问题在这里,但不知何故它没有找到帖子。

    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

我是一个真人,我靠支持 Discourse 为生已有十年,编写了许多导入程序,并从各种其他平台导入了一百多个论坛。

如果你想从 AI 助手那里获得帮助,请参阅 https://ask.discourse.com/

6 个赞

我设法让脚本函数正常工作了。

这是我的修改(得益于 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  # <-- 丢弃任何 nil 结果(孤立的 pid)
      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 检查
    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|
        # 如果 post 为 null 则跳过
		# 如果是 merged_post 则跳过
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # 如果已删除则跳过
        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 "未找到 ID 为 #{post["tid"]} 的主题,跳过"
          next
        end	

它现在似乎可以正常工作了。

虽然我不知道从 Discourse 内部架构的角度来看这有多正确,但乍一看它似乎是可行的。

非常欢迎任何改进和优化方面的建议。

1 个赞

提示:不要让它完全静默失败——将此更改为

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

否则,您会想知道为什么您的一半帖子丢失了,并且在数小时的沮丧之后,结果却是这个。

对于以下两个也一样

        next if post.nil?
        next if post["deleted"] == "1"
5 个赞

重写了 mongo.rb 文件中的 posts 方法

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

在此处输入引用的文本

此方法确保主题内的帖子按正确的时间顺序排序。升序。
注意 sort(score: 1) 调用。