Falha no upload de backup devido a TransferManager não inicializado

Após atualizar para v2026.8.0-latest.1 +4, meus backups começaram a falhar. Estes são os logs: log.txt.zip (22,1 KB)

Em particular, aqui está a seção que parece relevante:

[2026-08-04 03:37:52] Enviando arquivo compactado...
[2026-08-04 03:37:52] EXCEPTION: uninitialized constant Aws::S3::TransferManager
[2026-08-04 03:37:52] /var/www/discourse/lib/s3_helper.rb:453:in 'S3Helper#transfer_manager'
/var/www/discourse/lib/s3_helper.rb:332:in 'S3Helper#upload_file'
/var/www/discourse/lib/backup_restore/s3_backup_store.rb:48:in 'BackupRestore::S3BackupStore#upload_file'
/var/www/discourse/lib/backup_restore/creator.rb:437:in 'BackupRestore::Creator#upload_archive'
/var/www/discourse/lib/backup_restore/creator.rb:41:in 'BackupRestore::Creator#run'
/var/www/discourse/lib/backup_restore.rb:13:in 'BackupRestore.backup!'
/var/www/discourse/app/jobs/regular/create_backup.rb:10:in 'Jobs::CreateBackup#execute'
/var/www/discourse/app/jobs/base.rb:313:in 'block (2 levels) in Jobs::Base#perform'

Não tenho certeza de como resolver esse problema.

Atualizei para a v2026.9.0-latest +565, mas ainda encontrei exatamente o mesmo problema.

A versão do Discourse que você está executando inclui uma versão do aws-sdk-s3 que deve fornecer Aws::S3::TransferManager.

Você está usando a instalação oficial do Discourse via Docker? Se estiver usando o setup oficial do Docker, o contêiner foi totalmente recriado como parte da atualização?

Sim, acredito que estou usando a instalação oficial do Discourse via Docker. Existe alguma forma de eu ter certeza disso? Já faz um tempo desde que realizei a instalação.

Para atualizar, executei ./launcher rebuild app. Suponho que isso signifique que o container foi totalmente recriado, mas talvez eu esteja errado?

Alguns detalhes adicionais que podem ser relevantes:

  • Estou usando Ubuntu 24.04.4 LTS
  • O app.yml instala os plugins oficiais docker_manager e discourse-doc-categories no hook after_code. Não há outros plugins definidos no app.yml.
  • Estou tentando armazenar os backups em um armazenamento compatível com S3 fornecido pela Backblaze, e isso funcionava anteriormente.

Uma coisa que pode ajudar a estreitar a busca é verificar qual gem aws-sdk-s3 está sendo carregada de fato dentro do container em execução.

A alteração no Discourse que introduziu o Aws::S3::TransferManager também atualizou o aws-sdk-s3 da versão 1.182.0 para a 1.227.0:

Na revisão do Discourse que você vinculou, o Gemfile.lock ainda deve estar usando o aws-sdk-s3 1.227.0, que fornece o Aws::S3::TransferManager.

Você poderia tentar entrar no container:

cd /var/discourse
./launcher enter app

Em seguida, dentro do container:

cd /var/www/discourse
bundle info aws-sdk-s3
bundle exec ruby -e '
require "aws-sdk-s3"
puts "gem: #{Gem.loaded_specs["aws-sdk-s3"]&.full_name}"
puts "path: #{Gem.loaded_specs["aws-sdk-s3"]&.full_gem_path}"
p Aws::S3.autoload?(:TransferManager)
p Aws::S3::TransferManager
'

Também pode ser útil verificar a própria aplicação sob o Rails:

RAILS_ENV=production bundle exec rails runner '
puts "aws-sdk-s3: #{Gem.loaded_specs["aws-sdk-s3"]&.version}"
p Aws::S3.autoload?(:TransferManager)
p Aws::S3::TransferManager
'

Depois, saia do container com:

logout

Se isso reportar uma versão mais antiga do aws-sdk-s3, ou se o TransferManager estiver ausente, isso explicaria a exceção no backup e sugeriria que as gems instaladas no container não correspondem ao Gemfile.lock atual do Discourse.

Se reportar a versão 1.227.0 e resolver o Aws::S3::TransferManager com sucesso, então o problema é mais incomum e saberemos para onde olhar: o que difere entre o processo de backup do Sidekiq e aquele ambiente interativo.

Como você está usando ./launcher rebuild app, isso soa como o fluxo padrão do discourse_docker; os comandos acima devem nos dizer com mais precisão o que acabou ficando no container reconstruído.

Parece que eu tenho uma versão antiga do aws-sdk-s3.

$ bundle info aws-sdk-s3
  * aws-sdk-s3 (1.177.0)
	Summary: AWS SDK for Ruby - Amazon S3
	Homepage: https://github.com/aws/aws-sdk-ruby
	Source Code: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sdk-s3
	Changelog: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sdk-s3/CHANGELOG.md
	Path: /var/www/discourse/vendor/bundle/ruby/3.4.0/gems/aws-sdk-s3-1.177.0

Ao investigar isso, lembrei que eu usava este workaround para contornar um problema em que o AWS SDK não funcionava com o Backblaze. Ao me atualizar sobre o tópico, vi que o Backblaze atualizou sua API, o que torna o workaround desnecessário.

Depois que removi o workaround, o backup foi concluído com sucesso! Obrigado pela ajuda. :heart:

Uma reflexão a partir disso: modelos de compatibilidade temporários como este podem ser mais seguros se tiverem algum tipo de condição de autoexpiração/guarda, em vez de continuar sobrescrevendo as dependências do Discourse indefinidamente.

Por exemplo, com backports temporários de código-fonte, estou planejando usar hooks em produção que primeiro verifiquem se a correção upstream já está presente e apliquem o patch apenas se ainda for necessário.

Neste caso, algo ao longo dessas linhas poderia potencialmente ter impedido que o pin antigo aws-sdk-s3 1.177.0 continuasse uma vez que o próprio Discourse começou a depender de funcionalidades mais novas do SDK:

hooks:
  after_bundle_exec:
    - exec:
        cd: $home
        cmd:
          - |
            if grep -q "Aws::S3::TransferManager" lib/s3_helper.rb; then
              echo "Discourse agora requer Aws::S3::TransferManager; pulando o downgrade obsoleto do aws-sdk-s3 do Backblaze"
            else
              echo "Aplicando pin temporário de compatibilidade do aws-sdk-s3 do Backblaze"
              bundle config set frozen false
              sed -i 's/gem "aws-sdk-s3", require: false/gem "aws-sdk-s3", "1.177.0", require: false/' Gemfile
              bundle update aws-sdk-s3
              bundle add aws-sdk-core --version 3.215
            fi

Essa verificação específica é apenas ilustrativa - uma verificação de versão mais geral ou uma falha explícita quando o Discourse passar da versão de SDK esperada provavelmente seria melhor.

Mesmo falhar de forma explícita seria provavelmente preferível a silenciosamente produzir um container cujos gems não correspondem mais às versões esperadas pelo Discourse.

Embora eu concorde que falhar de forma explícita teria sido desejável, o caso era que, anteriormente, eu queria que as gems não corresendessem às versões esperadas pelo Discourse, porque a versão mais recente do aws-sdk-s3 enviava cabeçalhos que o Backblaze não suportava e eu queria que os backups funcionassem.

Não sei se havia alguma verificação que eu poderia ter configurado proativamente para me notificar quando o contorno (workaround) deixasse de ser necessário. Implementei o contorno há um ano e meio, em abril de 2025, antes que uma correção estivesse disponível.

Acho que o melhor que eu poderia ter feito foi adicionar uma data de expiração, para me forçar a verificar se o contorno ainda era necessário. Uma funcionalidade assim seria bem-vinda. Ou talvez a responsabilidade seja minha de ser mais proativo e configurar lembretes para verificar se o contorno ainda é necessário. Ter algumas anotações me ajudou nesse aspecto.

Um pequeno complemento, após eu mesmo ter enfrentado uma situação muito semelhante hoje.

Eu tinha um backport temporário de fonte em app.yml para um PR do Discourse que ainda não havia sido mesclado. O hook usava:

git apply --check /tmp/patch &&
  git apply /tmp/patch

O upstream, em seguida, refatorou uma das áreas afetadas, de modo que o patch antigo não se aplicava mais corretamente. O git apply --check fez exatamente o que eu queria: o patch não foi aplicado parcialmente, e a reconstrução falhou de forma explícita, em vez de gerar um checkout sutilmente inconsistente.

Havia, no entanto, uma desvantagem importante: como isso aconteceu durante o ./launcher rebuild app, a falha na reconstrução deixou o contêiner app indisponível até que eu removesse o hook obsoleto e refizesse a reconstrução.

Portanto, acho que eu refinaria minha sugestão anterior um pouco. Para soluções temporárias de produção/backports, a abordagem mais segura parece ser uma combinação de:

  • uma data de revisão/expiração explícita, como você sugeriu;
  • uma trava de versão/semântica, onde for possível torná-la confiável;
  • uma verificação de aplicabilidade prévia antes de iniciar uma reconstrução de produção, quando possível;
  • e ainda assim usar git apply --check imediatamente antes do git apply como última rede de segurança.

No meu caso, o lembrete de expiração/revisão teria me alertado para reavaliar a solução temporária, enquanto a verificação de aplicabilidade impediu que o patch desatualizado modificasse silenciosamente um checkout mais recente do Discourse.

Então, seu ponto sobre uma data de expiração faz mais sentido para mim agora: muitas vezes não há um teste automático perfeito para “esta solução temporária ainda é necessária?”, especialmente quando a solução original deliberadamente faz com que a instalação difira do upstream.