Cargas rotas con instalación de subcarpeta

Similar a Uploaded avatars and Gravatar not working with subfolder installation

Todas las cargas están rotas en mi instalación de subcarpeta. Las cargas llegan al directorio de cargas real, pero al renderizar las publicaciones, todas las imágenes obtienen src="".

Haciendo una publicación…
https://i.imgur.com/ofOUY4e.png

Después de publicar…
https://i.imgur.com/EBmnD6e.png

Sorprendentemente, si luego me muevo a un navegador diferente (ahora Chrome), abro el tema (donde la imagen todavía está rota), pero luego hago clic en editar, ¡la imagen se renderiza nuevamente en la vista previa de edición!

https://i.imgur.com/3rQirhc.png

Esto confirma que se está cargando correctamente en el servidor, lo cual he verificado:

root@cs6991:/var/discourse# ./launcher enter app
x86_64 arch detected.
root@cs6991-app:/var/www/discourse# ls 'public/~cs6991/forum'
backups  uploads
root@cs6991-app:/var/www/discourse# ls 'public/~cs6991/forum/uploads'
default
root@cs6991-app:/var/www/discourse# ls 'public/~cs6991/forum/uploads/default/original/1X/'
08335563eac3a393e60a902d4d38cffdfa6d967d.png  3eee67e6460792667bab4f2248ad4643be4feae3.png
29e403dabcfee32379629fb6d844354193e278ba.png  42ecfcb27b534acc9f3436fa7d291c2fca106e57.png

Pero simplemente no parece renderizarse en la página real.
El mismo problema ocurre con otras cargas, como los avatares.

Alguna información:

Subcarpeta: /~cs6991/forum

app.yml

## esta es la plantilla de contenedor de Discourse independiente todo en uno
##
## Después de realizar cambios en este archivo, DEBE reconstruir
## /var/discourse/launcher rebuild app
##
## ¡TENGA MUCHO CUIDADO AL EDITAR!
## ¡LOS ARCHIVOS YAML SON EXTREMADAMENTE SENSIBLES A ERRORES DE ESPACIO O ALINEACIÓN!
## visite http://www.yamllint.com/ para validar este archivo según sea necesario

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## Descomente estas dos líneas si desea agregar Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

## ¿qué puertos TCP/IP debe exponer este contenedor?
## Si desea que Discourse comparta un puerto con otro servidor web como Apache o nginx,
## consulte https://meta.discourse.org/t/17247 para obtener detalles
expose:
  - "80:80"   # http
  - "443:443" # https

params:
  db_default_text_search_config: "pg_catalog.english"

  ## Establezca db_shared_buffers en un máximo del 25% de la memoria total.
  ## se establecerá automáticamente mediante bootstrap según la RAM detectada, o puede anularlo
  db_shared_buffers: "128MB"

  ## puede mejorar el rendimiento de la clasificación, pero aumenta el uso de memoria por conexión
  #db_work_mem: "40MB"

  ## ¿Qué revisión de Git debe usar este contenedor? (predeterminado: tests-passed)
  #version: tests-passed

env:
  LC_ALL: en_US.UTF-8
  LANG: en_US.UTF-8
  LANGUAGE: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

  ## ¿Cuántas solicitudes web concurrentes se admiten? Depende de la memoria y los núcleos de CPU.
  ## se establecerá automáticamente mediante bootstrap según las CPU detectadas, o puede anularlo
  UNICORN_WORKERS: 2

  ## TODO: El nombre de dominio al que responderá esta instancia de Discourse
  ## Requerido. Discourse no funcionará con un número IP simple.
  DISCOURSE_HOSTNAME: 'cgi.cse.unsw.edu.au'

  ## Descomente si desea que el contenedor se inicie con el mismo
  ## nombre de host (-h option) que se especifica arriba (predeterminado "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: Lista de correos electrónicos separados por comas que serán administradores y desarrolladores
  ## en el registro inicial, por ejemplo, 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: '<<REDACTED>>'

  ## TODO: El servidor de correo SMTP utilizado para validar nuevas cuentas y enviar notificaciones
  # DIRECCIÓN SMTP, nombre de usuario y contraseña son requeridos
  # ADVERTENCIA: el carácter '#' en la contraseña SMTP puede causar problemas.
  DISCOURSE_SMTP_ADDRESS: email-smtp.ap-southeast-2.amazonaws.com
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: <<REDACTED>>
  DISCOURSE_SMTP_PASSWORD: <<REDACTED>>
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (opcional, predeterminado true)
  #DISCOURSE_SMTP_DOMAIN: discourse.example.com    # (requerido por algunos proveedores)
  DISCOURSE_NOTIFICATION_EMAIL: discourse@cs6991.email    # (dirección para enviar notificaciones)

  ## Si agregó la plantilla Lets Encrypt, descomente a continuación para obtener un certificado SSL gratuito
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  ## La dirección CDN http o https para esta instancia de Discourse (configurada para tirar)
  ## consulte https://meta.discourse.org/t/14857 para obtener detalles
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com

  ## La clave de licencia de IP de geolocalización de MaxMind para la búsqueda de direcciones IP
  ## consulte https://meta.discourse.org/t/-/137387/23 para obtener detalles
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

  DISCOURSE_RELATIVE_URL_ROOT: '/~cs6991/forum'

## El contenedor Docker no tiene estado; todos los datos se almacenan en /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Los plugins van aquí
## consulte https://meta.discourse.org/t/19157 para obtener detalles
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Cualquier comando personalizado para ejecutar después de la construcción
run:
  - exec: echo "Beginning of custom commands"
  ## Si desea establecer la dirección de correo electrónico 'De' para su primer registro, descomente y cambie:
  ## Después de recibir el primer correo electrónico de registro, vuelva a comentar la línea. Solo necesita ejecutarse una vez.
  #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"
  - exec:
      cd: $home
      cmd:
        - mkdir -p public/~cs6991/forum
        - cd public/~cs6991/forum && ln -s ../../uploads && ln -s ../../backups
  - replace:
     global: true
     filename: /etc/nginx/conf.d/discourse.conf
     from: proxy_pass http://discourse;
     to: |
        rewrite ^/(.*)$ /~cs6991/forum/$1 break;
        proxy_pass http://discourse;
  - replace:
     filename: /etc/nginx/conf.d/discourse.conf
     from: etag off;
     to: |
        etag off;
        location /~cs6991/forum {
           rewrite ^/~cs6991/forum/?(.*)$ /$1;
        }
  - replace:
       filename: /etc/nginx/conf.d/discourse.conf
       from: $proxy_add_x_forwarded_for
       to: $http_your_original_ip_header
       global: true
  - exec: echo "End of custom commands"

Todo lo demás, hasta donde puedo ver, parece estar funcionando correctamente, solo la renderización de cargas está actuando de manera bastante peculiar.

He verificado este comportamiento en una compilación completamente nueva; es decir, rm -rf /var/discourse, eliminando completamente docker, y siguiendo las instrucciones de instalación en la nube y de subcarpeta.

Si hay alguna investigación adicional que pueda realizar, estaré encantado de dar esos pasos. (¡perdón por los enlaces de imgur, todavía no tengo permitido más de 2 incrustaciones de imágenes aquí!)

¡Saludos!

1 me gusta

Información adicional: parece que la fuente ya se eliminó antes de renderizar, ya que falta en la base de datos de producción:

# sudo -u postgres psql discourse
discourse=# select * from posts where id=13;
 id | user_id | topic_id | post_number |                             raw                             |                                                  cooked                                                  |        created_at         |        updated_at         | reply_to_post_number | reply_count | quote_count |         deleted_at         | off_topic_count | like_count | incoming_link_count | bookmark_count | score | reads | post_type | sort_order | last_editor_id | hidden | hidden_reason_id | notify_moderators_count | spam_count | illegal_count | inappropriate_count |      last_version_at       | user_deleted | reply_to_user_id | percent_rank | notify_user_count | like_score | deleted_by_id | edit_reason | word_count | version | cook_method | wiki |          baked_at          | baked_version | hidden_at | self_edits | reply_quoted | via_email | raw_email | public_version | action_code | locked_by_id | image_upload_id
----+---------+----------+-------------+-------------------------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------------------+---------------------------+----------------------+-------------+-------------+----------------------------+-----------------+------------+---------------------+----------------+-------+-------+-----------+------------+----------------+--------+------------------+-------------------------+------------+---------------+---------------------+----------------------------+--------------+------------------+--------------+-------------------+------------+---------------+-------------+------------+---------+-------------+------+----------------------------+---------------+-----------+------------+--------------+-----------+-----------+----------------+-------------+--------------+-----------------
 13 |       1 |        7 |           2 | ![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png) | <p><img src="" alt="ferris" data-base62-sha1="5YA5Y9vjz0iQmn2DErtUBrHCKng" width="690" height="459"></p> | 2022-09-01 19:30:38.97281 | 2022-09-01 19:30:38.97281 |                      |           0 |           0 | 2022-09-01 19:47:34.612042 |               0 |          0 |                   0 |              0 |   0.2 |     1 |         1 |          2 |              1 | f      |                  |                       0 |          0 |             0 |                   0 | 2022-09-01 19:30:38.993775 | f            |                  |          0.5 |                 0 |          0 |             1 |             |          5 |       1 |           1 | f    | 2022-09-01 19:30:38.972751 |             2 |           |          0 | f            | f         |           |              1 |             |              |

También desde la pestaña de red, creando una respuesta con una imagen:

raw	"Mi+imagen+se+inserta+a+continuación:\n\n![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png)\n\n\nLa+imagen+está+arriba."
unlist_topic	"false"
category	"4"
topic_id	"7"
is_warning	"false"
archetype	"regular"
typing_duration_msecs	"7500"
composer_open_duration_msecs	"14116"
featured_link	""
shared_draft	"false"
draft_key	"topic_7"
image_sizes[https://cgi.cse.unsw.edu.au/~cs6991/forum/uploads/default/original/1X/29e403dabcfee32379629fb6d844354193e278ba.png][width]	"1200"
image_sizes[https://cgi.cse.unsw.edu.au/~cs6991/forum/uploads/default/original/1X/29e403dabcfee32379629fb6d844354193e278ba.png][height]	"800"
nested_post	"true"

o la cadena de consulta raw si se prefiere:

raw=Mi+imagen+se+inserta+a+continuación%3A%0A%0A!%5Bferris%7C690x459%5D(upload%3A%2F%2F5YA5Y9vjz0iQmn2DErtUBrHCKng.png)%0A%0A%0ALa+imagen+está+arriba.%26unlist_topic=false%26category=4%26topic_id=7%26is_warning=false%26archetype=regular%26typing_duration_msecs=7500%26composer_open_duration_msecs=14116%26featured_link=%26shared_draft=false%26draft_key=topic_7%26image_sizes%5Bhttps%3A%2F%2Fcgi.cse.unsw.edu.au%2F~cs6991%2Fforum%2Fuploads%2Fdefault%2Foriginal%2F1X%2F29e403dabcfee32379629fb6d844354193e278ba.png%5D%5Bwidth%5D=1200%26image_sizes%5Bhttps%3A%2F%2Fcgi.cse.unsw.edu.au%2F~cs6991%2Fforum%2Fuploads%2Fdefault%2Foriginal%2F1X%2F29e403dabcfee32379629fb6d844354193e278ba.png%5D%5Bheight%5D=800%26nested_post=true

la respuesta parece reflejar la publicación, y puedes ver que la fuente ya se ha eliminado en ese punto:

{
  "action": "create_post",
  "post": {
    "id": 16,
    "name": null,
    "username": "z.kologlu",
    "avatar_template": "/~cs6991/forum/letter_avatar_proxy/v4/letter/z/b9bd4f/{size}.png",
    "created_at": "2022-09-02T05:37:25.680Z",
    "cooked": "\u003cp\u003eMi imagen se inserta a continuación:\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"\" alt=\"ferris\" data-base62-sha1=\"5YA5Y9vjz0iQmn2DErtUBrHCKng\" width=\"690\" height=\"459\"\u003e\u003c/p\u003e\n\u003cp\u003eLa imagen está arriba.\u003c/p\u003e",
    "post_number": 5,
    "post_type": 1,
    "updated_at": "2022-09-02T05:37:25.680Z",
    "reply_count": 0,
    "reply_to_post_number": null,
    "quote_count": 0,
    "incoming_link_count": 0,
    "reads": 0,
    "readers_count": 0,
    "score": 0,
    "yours": true,
    "topic_id": 7,
    "topic_slug": "welcome-to-discourse",
    "display_username": null,
    "primary_group_name": null,
    "flair_name": null,
    "flair_url": null,
    "flair_bg_color": null,
    "flair_color": null,
    "version": 1,
    "can_edit": true,
    "can_delete": true,
    "can_recover": false,
    "can_wiki": true,
    "user_title": null,
    "bookmarked": false,
    "raw": "My image is inserted next:\n\n![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png)\n\n\nThe image is above.",
    "actions_summary": [
      {
        "id": 3,
        "can_act": true
      },
      {
        "id": 4,
        "can_act": true
      },
      {
        "id": 8,
        "can_act": true
      },
      {
        "id": 7,
        "can_act": true
      }
    ],
    "moderator": false,
    "admin": true,
    "staff": true,
    "user_id": 1,
    "draft_sequence": 12,
    "hidden": false,
    "trust_level": 1,
    "deleted_at": null,
    "user_deleted": false,
    "edit_reason": null,
    "can_view_edit_history": true,
    "wiki": false,
    "reviewable_id": null,
    "reviewable_score_count": 0,
    "reviewable_score_pending_count": 0
  },
  "success": true
}

Seguí rastreando este problema hasta esta función: discourse/app/models/post.rb at main · discourse/discourse · GitHub

Modifiqué mi función local:

  def cook(raw, opts = {})
    Rails.logger.info("Cocinando publicación con raw: #{raw}")
    # Para algunas publicaciones, por ejemplo, las importadas a través de RSS, admitimos HTML sin procesar. En ese
    # caso, podemos omitir el pipeline de renderizado.
    return raw if cook_method == Post.cook_methods[:raw_html]

    options = opts.dup
    options[:cook_method] = cook_method

    post_user = self.user
    options[:user_id] = post_user.id if post_user
    options[:omit_nofollow] = true if omit_nofollow?

    if self.with_secure_media?
      each_upload_url do |url|
        uri = URI.parse(url)
        if FileHelper.is_supported_media?(File.basename(uri.path))
          raw = raw.sub(
            url, Rails.application.routes.url_for(
              controller: "uploads", action: "show_secure", path: uri.path[1..-1], host: Discourse.current_hostname
            )
          )
        end
      end
    end

    cooked = post_analyzer.cook(raw, options)

    Rails.logger.info("Cocinado en: #{cooked}")

    new_cooked = Plugin::Filter.apply(:after_post_cook, self, cooked)

    if post_type == Post.types[:regular]
      if new_cooked != cooked && new_cooked.blank?
        Rails.logger.debug("El plugin está vaciando la publicación: #{self.url}\nraw: #{raw}")
      elsif new_cooked.blank?
        Rails.logger.debug("Se detectó publicación vacía: #{self.url}\nraw: #{raw}")
      end
    end

    Rails.logger.info("Nuevo cocinado en: #{new_cooked}")

    new_cooked
  end

Salida:

Completed 200 OK in 335ms (Views: 0.4ms | ActiveRecord: 0.0ms | Allocations: 78316)
done
done
Cooking post with raw: ![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png)
Started POST "/~cs6991/forum/presence/update" for 127.0.0.1 at 2022-09-02 05:55:33 +0000
Processing by PresenceController#update as */*
  Parameters: {"client_id"=>"16308337827949548cb8b156301a493b", "leave_channels"=>["/discourse-presence/reply/7"]}
Completed 200 OK in 19ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 6182)
done
Cooked into: <p><img src="" alt="ferris" data-base62-sha1="5YA5Y9vjz0iQmn2DErtUBrHCKng" width="690" height="459"></p>
New cooked into: <p><img src="" alt="ferris" data-base62-sha1="5YA5Y9vjz0iQmn2DErtUBrHCKng" width="690" height="459"></p>
done

Logré rastrear las cargas de publicaciones hasta esta línea exacta:

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/pretty-text/addon/sanitizer.js#L23

en particular,

  // relative urls
  if (/^\\/[\\w\\.\\-]+/i.test(href)) {
    return href;
  }

falla porque, dado que la URL de mi foro se sirve desde un directorio web de usuario de Apache (sobre el cual no tengo control), comienza con un ~, lo que rompe esa expresión regular.
He confirmado que modificar la clase de caracteres para incluir ~ (como [\\w\\.\\-~]) soluciona las cargas de publicaciones, ¡pero dolorosamente las cargas de avatares siguen rotas!

1 me gusta

…y la otra expresión regular rota:

el mismo problema: necesita un ~ en la clase de caracteres
lo que soluciona mis avatares

1 me gusta

Si Core no está tan interesado en una PR, probablemente podrías resolver eso permanentemente con un plugin personalizado para tu sitio específico.

1 me gusta