Migrar um fórum NodeBB com MongoDB para Discourse

Como você já sabe, o NodeBB suporta dois backends de banco de dados: Redis e MongoDB. O script de importação do Discourse suporta ambos. Neste tutorial, aprenderemos como migrar o NodeBB com MongoDB como backend de banco de dados. Usaremos o NodeBB Importer com o adaptador mongo. Se o seu fórum NodeBB usa Redis como backend, siga este tutorial, que demonstra o adaptador redis.

O plano

  • Preparar o ambiente de desenvolvimento.
  • Exportar o banco de dados do ambiente de produção.
  • Importar o banco de dados de produção para uma instância do Discourse.
  • Executar o script de importação.

O que pode ser migrado

  • Grupos
  • Anexos
  • Categorias
    • Categoria Raiz => Categoria Raiz
    • Subcategoria e Sub-subcategoria => Subcategoria
  • Tópicos e Posts
    • tópico fixado => tópico fixado
    • tópico bloqueado => tópico fechado
    • visualizações de tópicos
    • upvoted_by
    • estilos, menções, emojis e anexos.
  • Usuários (com os seguintes atributos)
    • plano de fundo do perfil
    • avatares
    • status de banido
    • nome de usuário
    • nome
    • e-mail
    • administrador
    • biografia
    • grupo
    • site
    • localização
    • data de ingresso

Preparando o Ambiente de Desenvolvimento Local

Como mencionado em nosso plano, primeiro precisamos preparar nosso ambiente de desenvolvimento. Siga um dos seguintes guias para instalar o próprio Discourse:

:bulb: Por favor, consulte este guia se tiver problemas ao configurar o servidor Discourse.

Em seguida, instale o servidor de banco de dados 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

Para mais detalhes, consulte o guia oficial.

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

Para mais detalhes, consulte o guia oficial.

Windows 10:

Baixe o instalador e instale o MongoDB como um serviço do Windows:

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

Execute o cmd com privilégios de administrador para verificar se o servidor mongo está funcionando corretamente:

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

Para mais detalhes, consulte o guia oficial.

Este ambiente será nosso servidor Discourse.

Exportando o Dump do Banco de Dados de Produção

Desligue seu fórum NodeBB (servidor de produção).

$ cd /path_to_nodebb
$ ./nodebb stop

Desligue seu banco de dados:

$ sudo service mongodb stop

Faça backup do seu banco de dados:

$ mongodump --out ~/my_backup_path/

A saída do comando anterior será algo como:

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)

Observe que o backup é na verdade um diretório, não apenas um arquivo.

:bulb: Você pode verificar o tamanho do seu banco de dados executando use myDatabase e depois db.stats().dataSize; dentro do CLI mongo. O valor retornado estará em bytes.

Faça backup dos ativos do fórum:

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

Após ter seu banco de dados e ativos do fórum, copie-os para o servidor Discourse.

Importando o Banco de Dados

Agora que temos nosso banco de dados, podemos importá-lo em nossa instância local do MongoDB:

$ mongorestore ~/path_of_my_backup_directory/

Para mais opções, consulte o guia oficial.

Em seguida, você precisa extrair o uploads.tar.gz para que o importador possa importar os ativos:

$ tar xvzf uploads.tar.gz

Executando o Script de Importação

Agora que nosso banco de dados e ativos estão prontos, estamos prontos para executar nosso script de importação. Antes disso, precisamos editar o script de importação do NodeBB para atender às nossas necessidades.

Este é o caminho da pasta de uploads do seu NodeBB:

ATTACHMENT_DIR = '/absolute_path/uploads'

Em seguida, precisamos informar ao importador para usar o adaptador mongo em vez do adaptador 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
# )

Execute o importador com Discourse limpo e suporte ao 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

O importador se conectará à instância do MongoDB e migrará tudo para o Discourse.

Após a conclusão do importador, inicie o Discourse:

$ bundle exec rails server

Inicie o Sidekiq para processar os dados migrados:

$ bundle exec sidekiq

Você pode monitorar o progresso em http://localhost:3000/sidekiq/queues.

Realize um backup do Discourse e faça o upload dele para seu servidor de produção do Discourse seguindo este tutorial.

:tada:

Se você tiver alguma dúvida sobre o processo, ficarei feliz em ajudar.

Boa migração :grinning:

10 curtidas

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 curtida

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 curtida

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'

Olá,

Obrigado pelo guia! Quando executo o script de migração, é isso que aparece quando o script chega ao ponto de importar os usuários:

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)

Consegui contornar isso comentando a parte que faz o upload das fotos de perfil; isso não é um problema.

Agora estou recebendo o erro nil ao importar tópicos. :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)

Caso alguém esteja interessado no futuro, consegui realizar a migração. Aqui estão alguns comentários sobre minha experiência; há algumas pegadinhas no processo.

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

2 curtidas
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 Estou enfrentando o mesmo erro que @OdysLam @FreeWorLD.

Olá!
Alguém já tentou migrar do NodeBB 4.9.x mais recente para o Discourse dev atual?

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

Estou com esse erro ao migrar no ambiente de desenvolvimento.
Alguma ideia?

Os scripts de migração ainda são suportados pela Comunidade em 2026?
O campo timestamp existe e contém dados válidos

Obrigado.

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

  post[\"timestamp\"] = timestamp_to_date(post[\"timestamp\"])\n\n[/quote]

O problema é que post é nil. Então você não configurou as coisas corretamente. Ele importou os usuários?

Podemos tentar, mas é bem limitado. É provável que você precise entender como um monte de coisas funcionam que podem estar além do que é facilmente suportável em um fórum.

3 curtidas
importando usuários
      182 / 182 (100.0%)  [437953 itens/min]
adicionando usuários a grupos...

importando tópicos...
      132 / 132 (100.0%)  [3567036 itens/min]
importando 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 <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>'


Os usuários parecem ter sido importados corretamente de acordo com a saída do script, veja acima.

Desculpe, você é uma pessoa viva ou apenas um assistente de IA inteligente o suficiente?

Então o problema tem a ver com o fato de não estar lendo os dados das postagens.

Minha suposição é que está aqui e por algum motivo não está encontrando as postagens.

    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

Eu sou uma pessoa viva que ganha a vida apoiando o Discourse há uma década e escreveu vários importadores e importou mais de cem fóruns de várias outras plataformas.

Se você quer ajuda de um assistente de IA, veja https://ask.discourse.com/

6 curtidas

De alguma forma consegui fazer a função do script funcionar corretamente.

Aqui estão minhas alterações (com uma pequena ajuda de um 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  # <-- remove quaisquer resultados nulos (pids órfãos)
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- verificação de nulo
    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|
        # pular se o post for nulo
		# pular se for merged_post
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # pular se estiver excluído
        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 "Tópico com id #{post["tid"]} não encontrado, pulando"
          next
        end	

Parece estar funcionando da maneira correta agora.

Embora eu não saiba o quão correto isso está do ponto de vista da arquitetura interna do Discourse, à primeira vista parece funcionar.

Quaisquer sugestões de melhoria e otimização são muito bem-vindas.

1 curtida

Dica: não deixe que falhe completamente em silêncio - mude isso para

if post.nil?
  puts "!!! Não foi possível encontrar a postagem #{id}"
  return nil
end

Ou você acabará se perguntando por que metade das suas postagens está faltando e, após horas de frustração, descobrirá que é isso.

o mesmo para estes dois

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

Método posts reescrito no arquivo 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

Insira o texto da citação aqui

Esta abordagem garante que as postagens dentro de um tópico sejam ordenadas na ordem cronológica correta. Ascendente.
Observe a chamada sort(score: 1).