Загрузка не работает при установке в подпапку

Аналогично Uploaded avatars and Gravatar not working with subfolder installation

Все загрузки не работают в моей установке в подпапке. Файлы действительно попадают в реальный каталог загрузок, но при отображении постов у всех изображений атрибут src="".

Создание поста…
https://i.imgur.com/ofOUY4e.png

После публикации…
https://i.imgur.com/EBmnD6e.png

Поразительно, но если я перейду в другой браузер (теперь Chrome), открою тему (где изображение всё ещё не отображается), а затем нажму изменить, изображение снова отобразится в предпросмотре редактирования!

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

Это подтверждает, что загрузка на сервер проходит успешно, что я проверил:

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

Но они просто не отображаются на самой странице.
Та же проблема возникает и с другими загрузками, например, с аватарами.

Некоторая информация:

Подпапка: /~cs6991/forum

app.yml

## это шаблон контейнера Docker Discourse «всё в одном»
##
## После внесения изменений в этот файл вы ДОЛЖНЫ выполнить пересборку
## /var/discourse/launcher rebuild app
##
## БУДЬТЕ ОЧЕНЬ ОСТОРОЖНЫ ПРИ РЕДАКТИРОВАНИИ!
## YAML-ФАЙЛЫ ЧРЕЗВЫЧАЙНО ЧУВСТВИТЕЛЬНЫ К ОШИБКАМ В ПРОБЕЛАХ И ВЫРАВНИВАНИИ!
## посетите http://www.yamllint.com/, чтобы проверить этот файл при необходимости

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
## Раскомментируйте эти две строки, если хотите добавить Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

## какие TCP/IP-порты должен открывать этот контейнер?
## Если вы хотите, чтобы Discourse использовал порт совместно с другим веб-сервером, например Apache или nginx,
## см. https://meta.discourse.org/t/17247 для деталей
expose:
  - "80:80"   # http
  - "443:443" # https

params:
  db_default_text_search_config: "pg_catalog.english"

  ## Установите db_shared_buffers максимум на 25% от общего объёма памяти.
  ## будет установлено автоматически при загрузке на основе обнаруженной оперативной памяти, либо вы можете переопределить
  db_shared_buffers: "128MB"

  ## может улучшить производительность сортировки, но увеличивает использование памяти на соединение
  #db_work_mem: "40MB"

  ## Какую ревизию Git должен использовать этот контейнер? (по умолчанию: 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

  ## Сколько одновременных веб-запросов поддерживается? Зависит от объёма памяти и количества ядер процессора.
  ## будет установлено автоматически при загрузке на основе обнаруженных процессоров, либо вы можете переопределить
  UNICORN_WORKERS: 2

  ## TODO: Доменное имя, на которое будет реагировать этот экземпляр Discourse
  ## Обязательно. Discourse не будет работать с чистым IP-адресом.
  DISCOURSE_HOSTNAME: 'cgi.cse.unsw.edu.au'

  ## Раскомментируйте, если хотите, чтобы контейнер запускался с тем же
  ## именем хоста (опция -h), что указано выше (по умолчанию "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: Список email-адресов через запятую, которые станут администраторами и разработчиками
  ## при первой регистрации, например 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: '<<REDACTED>>'

  ## TODO: SMTP-сервер для проверки новых аккаунтов и отправки уведомлений
  ## SMTP-адрес, имя пользователя и пароль обязательны
  # ВНИМАНИЕ: символ '#' в пароле SMTP может вызвать проблемы!
  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           # (опционально, по умолчанию true)
  #DISCOURSE_SMTP_DOMAIN: discourse.example.com    # (требуется некоторыми провайдерами)
  DISCOURSE_NOTIFICATION_EMAIL: discourse@cs6991.email    # (адрес, с которого отправляются уведомления)

  ## Если вы добавили шаблон Lets Encrypt, раскомментируйте ниже, чтобы получить бесплатный SSL-сертификат
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  ## Адрес CDN http или https для этого экземпляра Discourse (настроен на получение)
  ## см. https://meta.discourse.org/t/14857 для деталей
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com

  ## Ключ IP-адреса MaxMind для поиска по IP-адресам
  ## см. https://meta.discourse.org/t/-/137387/23 для деталей
  #DISCOURSE_MAXMIND_LICENSE_KEY: 1234567890123456

  DISCOURSE_RELATIVE_URL_ROOT: '/~cs6991/forum'

## Контейнер Docker не хранит состояние; все данные хранятся в /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Плагины размещаются здесь
## см. https://meta.discourse.org/t/19157 для деталей
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Любые пользовательские команды для запуска после сборки
run:
  - exec: echo "Начало пользовательских команд"
  ## Если вы хотите установить адрес «От» для первой регистрации, раскомментируйте и измените:
  ## После получения первого письма о регистрации закомментируйте строку обратно. Выполняется только один раз.
  #- 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 "Конец пользовательских команд"

Всё остальное, насколько я могу судить, работает корректно — только отображение загрузок ведёт себя странно.

Я проверил это поведение на совершенно свежей сборке — то есть выполнил rm -rf /var/discourse, полностью удалил docker и следовал инструкциям по установке в облаке + в подпапке.

Если есть дополнительные шаги для расследования, которые я могу предпринять, я с радостью их выполню. (извините за ссылки на Imgur — мне пока не разрешено вставлять более двух изображений!)

Ура!

1 лайк

Немного дополнительной информации — похоже, что src уже удаляется перед рендерингом, так как его нет в производственной базе данных:

# 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 |             |              |

Также из вкладки сети при создании ответа с изображением:

raw	"My+image+is+inserted+next:\n\n![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png)\n\n\nThe+image+is+above."
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"

или строка запроса в сыром виде, если предпочтительно:

raw=My+image+is+inserted+next%3A%0A%0A!%5Bferris%7C690x459%5D(upload%3A%2F%2F5YA5Y9vjz0iQmn2DErtUBrHCKng.png)%0A%0A%0AThe+image+is+above.&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%5Bhttps%3A%2F%2Fcgi.cse.unsw.edu.au%2F~cs6991%2Fforum%2Fuploads%2Fdefault%2Foriginal%2F1X%2F29e403dabcfee32379629fb6d844354193e278ba.png%5D%5Bwidth%5D=1200&image_sizes%5Bhttps%3A%2F%2Fcgi.cse.unsw.edu.au%2F~cs6991%2Fforum%2Fuploads%2Fdefault%2Foriginal%2F1X%2F29e403dabcfee32379629fb6d844354193e278ba.png%5D%5Bheight%5D=800&nested_post=true`

Ответ, похоже, возвращает пост обратно, и можно увидеть, что src уже на этом этапе удалён:

{
  "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": "<p>My image is inserted next:</p>\n<p><img src=\"\" alt=\"ferris\" data-base62-sha1=\"5YA5Y9vjz0iQmn2DErtUBrHCKng\" width=\"690\" height=\"459\"></p>\n<p>The image is above.</p>",
    "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
}

Продолжил отладку этой проблемы и дошел до этой функции: discourse/app/models/post.rb at main · discourse/discourse · GitHub

Изменил свою локальную функцию:

  def cook(raw, opts = {})
    Rails.logger.info("Обработка поста с raw: #{raw}")
    # Для некоторых постов, например импортированных через RSS, мы поддерживаем необработанный HTML.
    # В этом случае можно пропустить конвейер рендеринга.
    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("Обработано в: #{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("Плагин очищает пост: #{self.url}\nraw: #{raw}")
      elsif new_cooked.blank?
        Rails.logger.debug("Обнаружен пустой пост: #{self.url}\nraw: #{raw}")
      end
    end

    Rails.logger.info("Новый обработанный: #{new_cooked}")

    new_cooked
  end

Вывод:

Завершено 200 OK за 335 мс (Views: 0.4 мс | ActiveRecord: 0.0 мс | Allocations: 78316)
готово
готово
Обработка поста с raw: ![ferris|690x459](upload://5YA5Y9vjz0iQmn2DErtUBrHCKng.png)
Запущен POST "/~cs6991/forum/presence/update" для 127.0.0.1 в 2022-09-02 05:55:33 +0000
Обработка PresenceController#update как */*
  Параметры: {"client_id"=>"16308337827949548cb8b156301a493b", "leave_channels"=>["/discourse-presence/reply/7"]}
Завершено 200 OK за 19 мс (Views: 0.2 мс | ActiveRecord: 0.0 мс | Allocations: 6182)
готово
Обработано в: <p><img src="" alt="ferris" data-base62-sha1="5YA5Y9vjz0iQmn2DErtUBrHCKng" width="690" height="459"></p>
Новый обработанный: <p><img src="" alt="ferris" data-base62-sha1="5YA5Y9vjz0iQmn2DErtUBrHCKng" width="690" height="459"></p>
готово

Удалось отследить загрузку постов до этой конкретной строки:

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

в частности,

  // относительные URL
  if (/^\/[\w\.\-]+/i.test(href)) {
    return href;
  }

работает некорректно, поскольку URL моего форума обслуживается из директории пользователя Apache (которой я не управляю), начинается с ~, что нарушает работу этого регулярного выражения.
Я подтвердил, что изменение класса символов на включение ~ (как [\w\.\-~]) исправляет загрузку постов, но, к сожалению, загрузка аватаров всё ещё не работает!

1 лайк

…и ещё один сломанный регулярный выражение:

та же история — в классе символов нужен ~
что исправляет мои аватары

1 лайк

Если Core не особо заинтересован в PR, вы, вероятно, можете навсегда решить эту проблему с помощью кастомного плагина для вашего конкретного сайта.

1 лайк