¿Cómo encontrar cualquier imagen faltante?

Claro. Aquí está el texto raw:

"Parece que \"Fung-Wong\", \"Mario\" o simplemente \"Tifón #16\" [tocará tierra en Japón este jueves](http://www.jma.go.jp/jp/typh/1416l.html):\n\n![Tifón 16](/uploads/default/35/4608d96d1b27846f.png)"

Y aquí está el texto cooked:

"<p>Parece que “Fung-Wong”, “Mario” o simplemente “Tifón <span class=\"hashtag\">#16</span>” <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">tocará tierra en Japón este jueves</a>:</p>\n<p><div class=\"lightbox-wrapper\"><a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\"><img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Tifón 16\" width=\"602\" height=\"500\"><div class=\"meta\">\n<svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#far-image\"></use></svg><span class=\"filename\">4608d96d1b27846f.png</span><span class=\"informations\">800×664</span><svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#discourse-expand\"></use></svg>\n</div></a></div></p>"

Son un poco difíciles de leer cuando están comprimidos en una sola línea, así que aquí está el texto raw con formato:

"Parece que \"Fung-Wong\", \"Mario\" o simplemente
\"Tifón #16\" <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">tocará tierra en Japón este jueves</a>:\n\n
![Tifón 16](/uploads/default/35/4608d96d1b27846f.png)"

Y aquí está el texto cooked con formato:

"<p>
  Parece que “Fung-Wong”, “Mario” o simplemente
  “Tifón <span class=\"hashtag\">#16</span>” <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">tocará tierra en Japón este jueves</a>:
</p>\n
<p>
  <div class=\"lightbox-wrapper\">
    <a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\">
      <img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Tifón 16\" width=\"602\" height=\"500\">
      <div class=\"meta\">\n
        <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
          <use xlink:href=\"#far-image\"></use>
        </svg>
        <span class=\"filename\">4608d96d1b27846f.png</span>
        <span class=\"informations\">800×664</span>
        <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
          <use xlink:href=\"#discourse-expand\"></use>
        </svg>\n
      </div>
    </a>
  </div>
</p>"

Por si acaso, hay bastantes (más de un centenar) publicaciones como esta en mi sitio:

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

No deberías tener formatos de URL diferentes en el contenido crudo y el cocido. ¿Podrías intentar volver a hornear la publicación anterior? Puedes hacerlo mediante el menú de la publicación Reconstruir HTML o con el comando post.rebake!. ¿Existen campos personalizados de la publicación relacionados con “uploads” en esta publicación? Puedes leer todos los campos personalizados con el comando post.custom_fields.

Aquí están todos los demás campos personalizados de esa publicación en particular (antes de ejecutar el comando Rebuild HTML):

  id: 43,
  user_id: 1,
  topic_id: 36,
  post_number: 3,
  created_at: Mon, 22 Sep 2014 05:05:16 UTC +00:00,
  updated_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  reply_to_post_number: nil,
  reply_count: 0,
  quote_count: 0,
  deleted_at: nil,
  off_topic_count: 0,
  like_count: 0,
  incoming_link_count: 0,
  bookmark_count: 0,
  avg_time: 58,
  score: 1.2,
  reads: 6,
  post_type: 1,
  sort_order: 3,
  last_editor_id: -1,
  hidden: false,
  hidden_reason_id: nil,
  notify_moderators_count: 0,
  spam_count: 0,
  illegal_count: 0,
  inappropriate_count: 0,
  last_version_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  user_deleted: false,
  reply_to_user_id: nil,
  percent_rank: 0.585365853658537,
  notify_user_count: 0,
  like_score: 0,
  deleted_by_id: nil,
  edit_reason: "descargué copias locales de las imágenes",
  word_count: 34,
  version: 2,
  cook_method: 1,
  wiki: false,
  baked_at: Sun, 14 Apr 2019 09:28:00 UTC +00:00,
  baked_version: 2,
  hidden_at: nil,
  self_edits: 2,
  reply_quoted: false,
  via_email: false,
  raw_email: nil,
  public_version: 2,
  action_code: nil,
  image_url: "/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png",
  locked_by_id: nil

No veo un campo uploads, pero ¿quizás image_url es lo que buscas? Su valor—antes de ejecutar el comando Rebuild HTML—era:

/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

Parece que ejecutar el comando Rebuild HTML cambió el valor del campo image_url a:

https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png

Todas las URLs en el texto procesado también parecen haberse actualizado:

"<p>
  Parece que "Fung-Wong", "Mario", o simplemente
  "Tifón <span class=\"hashtag\">#16</span>" estará
  <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">
    haciendo tierra en Japón el jueves
  </a>:
 </p>\n
 <p>
   <div class=\"lightbox-wrapper\">
     <a class=\"lightbox\" href=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\">
       <img src=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\" alt=\"Tifón 16\" width=\"602\" height=\"500\">
       <div class=\"meta\">\n
         <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#far-image\"></use>
         </svg>
         <span class=\"filename\">4608d96d1b27846f.png</span>
         <span class=\"informations\">800×664</span>
         <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#discourse-expand\"></use>
         </svg>\n
       </div>
     </a>
   </div>
 </p>"

¿Cuál es la relación entre 4608d96d1b27846f.png y 01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png? Tienen las mismas dimensiones y parecen idénticas a primera vista, pero claramente son archivos diferentes:

$ diff /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
Binary files /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png and /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png differ

$ ls -l /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png
-rw-r--r-- 1 chris www-data 150319 Jan 19 01:14 /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png

$ ls -l /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
-rw-r--r-- 1 chris chris 95005 Jul  3 15:25 /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

Y, por supuesto, la pregunta del millón sigue en pie: ¿Cómo debería proceder para migrar /uploads/default/35/4608d96d1b27846f.png al nuevo esquema de subida?

Parece que tus cargas no se han migrado correctamente al nuevo esquema. El cambio en SiteSetting.migrate_to_new_scheme = true debería encargarse de esta situación. No estoy seguro de por qué no ocurre en tu caso. Por favor, verifica la cantidad de cargas que no se han migrado al nuevo esquema. Ejecuta los siguientes comandos para obtener los resultados.

Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count

No, estos no son campos personalizados. Debes obtener los valores de los campos personalizados con el comando post.custom_fields.

¡Oh, lo siento! Malinterpreté completamente lo que querías decir con los campos personalizados.

Podría estar equivocado, pero no parece que esa publicación en particular tenga ninguno:

[1] pry(main) > Post.find_by(:id => 43).custom_fields
=> {}

Bueno, esto es interesante…

[2] pry(main) > Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
=> 0
[3] pry(main) > Post.find_by(:id => 43).image_url
=> "https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png"

Parece que no hay resultados que coincidan con la consulta que proporcionaste. ¿Es este el comportamiento esperado?

Además:

¿Tienes alguna idea de cuál podría ser la respuesta a esta pregunta?

Parece que tus cargas ya han sido migradas al nuevo esquema. Sin embargo, los mensajes no se han reasignado correctamente a las nuevas URLs del esquema. Esos mensajes ahora están en un estado desordenado. ¿Podrías enviarme las credenciales de tu sitio por mensaje privado? Así podré investigarlo más a fondo cuando tenga tiempo libre.

Ah, eso es lo que temía. Lamentablemente, esta es una instalación privada y no estoy seguro de tener permiso para conceder acceso (root) al servidor a una parte externa. ¿No hay nada más que pueda hacer para solucionar el problema?

He tenido imágenes faltantes desde finales de abril, aunque recién esta noche he comenzado a investigar el problema.

rake posts:missing_uploads
Faltan 26766 subidas de publicaciones.

Faltan 22693 subidas.
22683 de 22693 son subidas del esquema antiguo.
Se ven afectadas 9352 de 535188 publicaciones.

Estamos en la versión estable… considerando las últimas publicaciones en este hilo, no estoy seguro de qué hacer a continuación.

Edición: Elegí un archivo GIF en particular y descubrí que faltaba en el directorio de subidas del servidor en vivo; sin embargo, sí está presente en el directorio tombstone de mi servidor de respaldo. Copié este archivo desde mi servidor de respaldo (discourse/shared/standalone/uploads/tombstone/default/39/ee8670816301d4c4.gif) al directorio tombstone correspondiente en el servidor en vivo y luego volví a ejecutar la tarea rake anterior como prueba.

La imagen ahora se muestra en la publicación en cuestión y los números generales han bajado a:

Faltan 26750 subidas de publicaciones.

Faltan 22692 subidas.
22682 de 22692 son subidas del esquema antiguo.
Se ven afectadas 9336 de 535190 publicaciones.

Parece que el directorio tombstone en el servidor en vivo ocupa 138 MB, mientras que en el servidor de respaldo ocupa 9,5 GB. Por lo tanto, voy a sincronizar este directorio con rsync y volver a ejecutar la tarea rake, con la esperanza de que reduzca aún más los conteos reportados.

@kansaichris parece que solo tienes 3 publicaciones con cargas de archivos faltantes. En ese caso, debes editar manualmente el contenido crudo de la publicación con la URL de carga correcta.

@skl tienes muchas cargas del esquema antiguo. Después de copiar las cargas de marcadores de posición desde el servidor de copia de seguridad, debes ejecutar los siguientes comandos para migrarlas al nuevo esquema.

rake posts:missing_uploads    # asegúrate de que `rsync` haya copiado los archivos
rake uploads:recover          # si hay cargas faltantes después de rsync
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
# asegúrate de que el conteo sea 0
rake posts:missing_uploads    # verifica el estado nuevamente

Gracias por la ayuda, @vinothkannans. Seguí tus instrucciones (tardé unas 12 horas en ejecutarlas) y los números han disminuido algo:

Faltan 22.614 subidas de publicaciones.
Faltan 19.830 subidas.
De 19.830, 19.821 son subidas con el esquema antiguo.
7.339 de 535.224 publicaciones están afectadas.

Como aún hay subidas faltantes, busqué fuera de la carpeta tombstone y descubrí que en el servidor en vivo, uploads/default muestra 22.885 directorios vacíos (en el servidor de respaldo hay 10 directorios vacíos). También hay una diferencia de tamaño de más de 10 GB en el respaldo, así que voy a sincronizar uploads/default desde el respaldo al servidor en vivo con rsync y luego volveré a ejecutar tus instrucciones.

Edición: rake posts:missing_uploads parece ser una tarea limitada por la CPU y de un solo hilo que lleva funcionando más de 30 horas, así que he escalado temporalmente el servidor a una instancia dedicada de CPU. Las imágenes parecen haberse recuperado por ahora, aunque en el esquema antiguo, por lo que presumiblemente alguna actualización de Discourse causó la eliminación original en primer lugar.

Hmm… si realmente solo hay 3 publicaciones con cargas de archivos faltantes, ¿por qué parecen haber 135 publicaciones cuyo texto crudo utiliza el antiguo esquema de carga, aunque el texto cocinado utilice el nuevo esquema?

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

Debido a las discrepancias en el esquema de las URLs de carga en las columnas raw y cooked. La tarea rake posts:missing_uploads verificará las cargas únicamente contra la columna cooked. De alguna manera tienes que corregir esas URLs de carga que no coinciden. No puedo ayudarte sin revisar la base de datos.

:crossed_fingers:

Ah, vale, no había caído en que la tarea posts:missing_uploads solo verifica la columna cooked; eso sin duda explicaría la discrepancia. :+1:

¿Es correcto decir que el proceso de migración iniciado al establecer SiteSetting.migrate_to_new_scheme en true también solo verifica el valor de la columna cooked?

La nueva migración del esquema reemplazará las URLs tanto en raw como en cooked (discourse/app/models/upload.rb at 73a45048a015890a4bce6ec7c203430900adfa02 · discourse/discourse · GitHub). Pero en tu caso no ha ocurrido.

Creo que sí. También puedes verificar la columna last_updated_at de los mensajes afectados.

La tarea se abortó con un error y muestra el mismo error en cada intento:

[2019-07-26T09:18:56.829375 #572]  WARN -- : IFD mal formado: método `map' no definido para nil:NilClass
....¡rake abortado!
ArgumentError: longitud negativa -2 proporcionada
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:89:in `readframe'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:116:in `examine'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `block in initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `open'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `new'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `oriented?'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:27:in `optimize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (5 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/handler.rb:41:in `process'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (4 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `each'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `block (3 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:247:in `block in with_timeout'
Tareas: TOP => posts:missing_uploads

¿Estás en la última versión o en alguna anterior?

Última versión estable “2.3.2 +4”

Lo más probable es que necesites estar en la última versión beta.

Si estás autoalojando, no hay mucha razón para estar en la versión estable. De hecho, es más difícil de mantener.

El cliente ha solicitado específicamente que permanezca en la versión estable. Fue tajante al respecto cuando heredé el proyecto.