Les sauvegardes vers Cloudflare R2 échouent lors du téléversement multipart avec aws-sdk-s3 1.182.0 (méthode undefined 'downcase' pour nil)

Priorité/Sévérité :

Élevée pour les instances auto-hébergées utilisant un stockage de sauvegarde compatible S3, car les sauvegardes planifiées échouent après la création de l’archive.

Plateforme :

Discourse auto-hébergé sur la dernière branche.

Version en production actuelle lors de l’observation : v2026.4.0-latest

Ruby : 3.4.0

aws-sdk-s3 : 1.182.0

Description :

Les sauvegardes vers Cloudflare R2 échouent à l’étape finale « Téléchargement de l’archive… ».

Le dump de la base de données et la création de l’archive locale se déroulent avec succès, mais le téléchargement multipart de l’archive de sauvegarde terminée échoue.

Résultat réel :

La sauvegarde échoue avec :

EXCEPTION: multipart upload failed: undefined method 'downcase' for nil

La pile d’appels inclut :

aws-sdk-s3-1.182.0/lib/aws-sdk-s3/multipart_file_uploader.rb
lib/backup_restore/s3_backup_store.rb:48
lib/backup_restore/creator.rb:434

Résultat attendu :

L’archive de sauvegarde devrait être téléchargée avec succès vers le magasin de sauvegarde compatible S3 configuré.

Étapes pour reproduire :

  1. Configurer le stockage de sauvegarde vers Cloudflare R2 en utilisant le chemin de sauvegarde compatible S3.
  2. Utiliser une archive de sauvegarde supérieure au seuil multipart.
  3. Lancer une sauvegarde manuelle ou planifiée.
  4. Constater l’échec durant « Téléchargement de l’archive… ».

Configuration pertinente :

  • DISCOURSE_BACKUP_LOCATION=s3
  • DISCOURSE_S3_ENDPOINT=https://.r2.cloudflarestorage.com
  • DISCOURSE_S3_FORCE_PATH_STYLE=true
  • DISCOURSE_S3_BACKUP_BUCKET=
  • AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
  • AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED

Extrait de la pile d’appels observée :

algorithm = resp.context.params[:checksum_algorithm]
k = "checksum_#{algorithm.downcase}".to_sym

Cela suggère que checksum_algorithm est nil dans le chemin du téléchargement multipart.

Contexte supplémentaire :

Il existe un sujet Meta récent similaire concernant Backblaze B2 :

De plus, les entrées du journal des modifications (changelog) d’aws-sdk-s3 après la version 1.182.0 semblent pertinentes :

  • 1.201.0 : Correction du téléchargement multipart pour respecter le mode request_checksum_calculation when_required
  • 1.210.2 : Recours aux en-têtes de somme de contrôle de la requête lors de l’utilisation de points de terminaison personnalisés ou de fournisseurs de points de terminaison pour les opérations PutObject et UploadPart

Discourse main semble toujours verrouiller aws-sdk-s3 sur la version 1.182.0 :

https://raw.githubusercontent.com/discourse/discourse/main/Gemfile.lock

Je n’ai pas regardé cela depuis un moment, mais voici comment j’ai géré la situation par le passé :

Ils ne considèrent pas cela comme un bug car ils ne prétendent pas prendre en charge tous les services S3 (pas tout à fait compatibles) sur la planète.

Je ne me souviens plus exactement pour quels sites je faisais cela, mais je n’ai pas souvenir d’avoir modifié quoi que ce soit récemment à ce sujet. Je pense donc que c’est toujours la meilleure solution de contournement.

Je crois qu’il y avait d’autres sujets à ce sujet lorsque AWS a publié la nouvelle bibliothèque qui a cassé les offres de tous les autres.

2 « J'aime »

Merci, cela a résolu le problème pour moi.

J’ai appliqué la solution de contournement directement dans app.yml via after_bundle_exec, en verrouillant aws-sdk-s3 à la version 1.177.0 et aws-sdk-core à la version 3.215, puis j’ai reconstruit le conteneur. Depuis, les sauvegardes manuelles vers Cloudflare R2 fonctionnent à nouveau, et les téléversements par navigateur qui échouaient précédemment fonctionnent également.

Dans mon cas, les erreurs s’affichaient sous la forme multipart upload failed: undefined method 'downcase' for nil avec aws-sdk-s3 1.182.0.

Je remercie pour cette solution de contournement.

1 « J'aime »

Correction… cette solution de contournement a fonctionné pour moi aussi, il fallait juste la placer dans son propre bloc hooks: et ne pas l’ajouter à un bloc hooks: existant, quelle bêtise de ma part.