Отлично, я решил проблему с помощью Claude и множества похвал. Я делюсь тем, что сделал, чтобы помочь любому, у кого возникла подобная или та же самая проблема.
Я не уверен, что это самый умный и оптимальный метод, просто тот, который сработал для меня.
Пожалуйста, будьте осторожны и помните, что я не эксперт, а новичок, который постоянно учится.
Проблема (S3 → локальная файловая система)
После миграции с AWS S3 на локальную файловую систему множество изображений отображалось как transparent.png. Файлы всегда были на диске, но Discourse не мог их найти.
Корень проблемы — разорванная цепочка:
- Посты с короткими URL вида
upload://(SHA1, закодированный в base62). - База данных
uploads, сопоставляющая SHA1 → локальный путь к файлу. - Файловая система, где файлы хранятся с именами, равными их SHA1-хешу.
Миграция корректно переместила файлы на диск, но записей uploads в базе данных не существовало. Без соответствующей записи Discourse возвращается к transparent.png.
Решение (создание записей и повторная обработка)
# Вход в контейнер
./launcher enter app
rails c
Создание отсутствующих записей загрузок из сиротских файлов:
dir = Rails.root.join("public", "uploads", "default", "original")
created = 0
Dir.glob(dir.join("**", "*")).select { |f| File.file?(f) }.each do |path|
sha = File.basename(path, File.extname(path))
next if Upload.find_by(sha1: sha)
ext = File.extname(path).delete(".")
relative = path.sub("#{Rails.root}/public", "")
u = Upload.new
u.sha1 = sha
u.url = relative
u.original_filename = File.basename(path)
u.filesize = File.size(path)
u.extension = ext
u.user_id = -1
u.save!(validate: false)
created += 1
puts "Created upload #{u.id}: #{sha}"
end
puts "Total created: #{created}"
Повторная обработка постов, ссылающихся на восстановленные загрузки:
fixed_posts = 0
Upload.where(user_id: -1).find_each do |u|
short = u.short_url
next unless short
Post.where("raw LIKE '%upload://%'").find_each do |p|
urls = p.raw.scan(/upload:\/\/[^\s\]\)]+/)
urls.each do |url|
decoded = Upload.sha1_from_short_url(url)
if decoded == u.sha1
p.rebake!
fixed_posts += 1
puts "Rebaked post #{p.id}"
break
end
end
end
end
puts "Total rebaked: #{fixed_posts}"
Перегенерация отсутствующих оптимизированных версий:
После исправления исходных файлов необходимо заполнить оптимизированные версии (1X, 2X и т.д.).
Rake работает в контейнере Discourse, но не в консоли Rails.
rake uploads:regenerate_missing_optimized
[ОПЦИОНАЛЬНО] Всё ещё отсутствуют оптимизированные версии
Если rake uploads:regenerate_missing_optimized не решил все проблемы с файлами и некоторые файлы всё ещё отсутствуют:
# Вход в контейнер
./launcher enter app
rails c
missing = 0
OptimizedImage.find_each do |oi|
path = "#{Rails.root}/public#{oi.url}"
unless File.exist?(path)
missing += 1
oi.delete
end
end
puts "Deleted #{missing} broken optimized records"
Затем выйдите из Rails и выполните снова:
rake uploads:regenerate_missing_optimized
Безопасный откат (на всякий случай)
Все созданные записи используют user_id: -1, а метод delete_all пропускает колбэки, поэтому файлы на файловой системе не затрагиваются. Чтобы отменить изменения:
Upload.where(user_id: -1).delete_all
Ранее по ошибке использовал
destroy_all, что вызвало колбэки и переместило файлы в «могилу» (tombstone).Восстановил один отдельный файл, который использовал для тестирования, и пересмотрел свой подход.