Ошибка Bootstrap при установке Discourse: ENOENT - /etc/runit/1.d/letsencrypt

Привет! Я пытаюсь установить Discourse, используя стандартный процесс ./launcher rebuild app, но во время этапа bootstrap возникает ошибка:

FAILED
--------------------
Errno::ENOENT: No such file or directory @ rb_sysopen - /etc/runit/1.d/letsencrypt
Location of failure: /usr/local/lib/ruby/gems/3.3.0/gems/pups-1.3.0/lib/pups/replace_command.rb:11:in `read'
replace failed with the params {"filename"=>"/etc/runit/1.d/letsencrypt", "from"=>"/--keylength/", "to"=>"-d forum.mysite.org --keylength"}
bootstrap failed with exit code 1
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.

Похоже, ошибка вызвана плагином, который пытается выполнить команду replace над файлом /etc/runit/1.d/letsencrypt, которого не существует внутри контейнера на этапе bootstrap. Соответствующая строка в плагине выглядит так:

- replace:
    filename: "/etc/runit/1.d/letsencrypt"
    from: "/--keylength/"
    to: "-d forum.mysite.org --keylength"

Подскажите, пожалуйста, как исправить это или корректно перегенерировать отсутствующий файл?

Заранее спасибо.

Плагин? Это же код cups из вашего app.yml, верно? Вы пытаетесь добавить ещё один сертификат? Например, как в Set up Let’s Encrypt with multiple domains / redirects Можете привести реальный код и оба сертификата?

Как вы правильно заметили, runit больше не существует, и теперь вся эта магия находится в /usr/local/bin/letsencrypt (внутри контейнера)

Я думаю, вам, возможно, нужно что-то вроде этого, если ваш сайт www.mysite.org и вы также хотите получить сертификат для forum.mysite.org:

- replace:
    filename: "/usr/local/bin/letsencrypt"
    from: "/-d www.mysite.org/"
    to: "-d www.mysite.org -d forum.mysite.org "
    global: true

То, что я бы сделал (что может быть вам не очень поможет), — это войти в контейнер, выполнить apt update; apt install -y vim, а затем отредактировать /usr/local/bin/letsencrypt, чтобы он запрашивал нужные вам сертификаты.

В тему про Let’s Encrypt, на которую я дал ссылку выше, я добавил код, который позволит вам ввести свой домен и получить код, который можно скопировать и вставить в ваш app.yml.

Я столкнулся с тем, что, похоже, является тем же сообщением об ошибке при попытке обновления.

Всё работало нормально, пока мне не сообщили, что необходимо критическое обновление.

После нажатия на кнопку обновления Docker Manager через страницу обновлений форума (до обновления остальной части системы) я получил следующий результат:

Вы используете старую версию образа Discourse
Обновления через веб-интерфейс отключены, пока вы не запустите последний образ. Для этого войдите на свой сервер по SSH и выполните:

    cd /var/discourse
    ./launcher rebuild app

Следование этим инструкциям привело к следующему результату в PuTTY на втором шаге:

Errno::ENOENT: No such file or directory @ rb_sysopen - /etc/runit/1.d/letsencrypt
Место сбоя: /usr/local/lib/ruby/gems/3.3.0/gems/pups-1.3.0/lib/pups/replace_command.rb:11:in `read'
Замена не удалась с параметрами {"filename"=>"/etc/runit/1.d/letsencrypt", "from"=>"/--keylength/", "to"=>"-d www.nzarchitecture.net.nz --keylength"}
Загрузка не удалась с кодом выхода 1

Я не пытаюсь ничего менять, связанное с сертификатами. Также я не планирую настраивать несколько доменов или перенаправления.

Как и у автора оригинального поста, мой файл app.yml сейчас выглядит так:

 after_ssl:
    - replace:
        filename: "/etc/runit/1.d/letsencrypt"
        from: /--keylength/
        to: "-d www.nzarchitecture.net.nz --keylength"

Я последовал вашей рекомендации:

 apt update;apt install -y vim

но результат был следующим:

vim уже является последней версией (2:9.1.0016-1ubuntu7.8).

Что касается второго предложенного шага, я не знаю, какие сертификаты мне нужны, так как не планировал ничего менять.

Я проверил каталог /usr/local/bin/letsencrypt, но он пуст.

Извините за моё полное незнание в этой области!

Как мне вернуть форум в рабочее состояние, не прибегая к восстановлению из резервной копии, которую потом всё равно придётся обновлять?

Будет идеально, если это возможно, предоставить простую последовательность команд, которые можно просто вставить в PuTTY.

Поскольку я явно не единственный, кто столкнулся с этой проблемой, возможно ли, что в стандартном скрипте обновления Docker Manager, вызываемом с веб-сайтов форумов, нужно внести изменения, чтобы избежать этой ошибки у других пользователей?

Хорошо, после нескольких часов мучений мне удалось снова запустить систему.

Я нашел старый файл app.yml и заменил его, просто удалив старые ссылки на плагины, которые с тех пор были включены в Discourse.

Этот более старый файл app.yml не содержал кода ниже, который я нашел в более новой версии.

after_ssl:
    - replace:
        filename: "/etc/runit/1.d/letsencrypt"
        from: /--keylength/
        to: "-d www.nzarchitecture.net.nz --keylength"

Я не помню, чтобы добавлял этот код сам, хотя настраивал свой сайт для использования Let’s Encrypt для получения бесплатных сертификатов безопасности, но инструкции по адресу Set up HTTPS support with Let's Encrypt, похоже, вообще не требуют этих строк, поэтому понятия не имею, для чего они были нужны.

Могло ли что-то еще потенциально записать эти строки в app.yml? Например, могли ли они быть добавлены во время бета-обновления?

По крайней мере, на данный момент, после удаления этих строк мой сайт снова работает и обновлен.

Когда мой текущий SSL-сертификат истечет, я, вероятно, узнаю, для чего были нужны эти дополнительные строки.

Ну да, вы это делаете, просто вы этого не помните. :wink:

Если у вас есть секция after_ssl в вашем yml-файле, значит, в какой-то момент вы настроили систему так, чтобы при вводе www перед вашим доменом запрос перенаправлялся на адрес с www, а затем автоматически перенаправлялся на адрес без www без ошибки сертификата.

Моя рекомендация заключалась в том, чтобы установить vim внутри контейнера и попробовать отредактировать файлы там, но я думаю, что мой код для добавления в app.yml должен сработать.

Верно. А теперь, если вы перейдёте по адресу https://www.nzarchitecture.net.nz/, вы увидите ошибку сертификата. Вы можете воспользоваться сервисом https://forcewww.com/.

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

Правильно ли я понимаю, что ваш код должен выглядеть примерно так, если я хочу, чтобы www.nzarchitecture.net.nz покрывался тем же сертификатом letsencrypt, что и nzarchitecture.net.nz?

after_ssl:
    - replace:
        filename: /usr/local/bin/letsencrypt
        from: /-d nzarchitecture.net.nz /
        to: "-d nzarchitecture.net.nz -d www.nzarchitecture.net.nz "
        global: true

Я попытался добавить этот блок (отличная терминология, спасибо!) в конец файла app.yml вместо оригинального блока ’after_ssl:’, и теперь я могу пересобрать Discourse без ошибок — но это, похоже, мне не помогает; мой браузер всё ещё выдаёт ответ ‘net::ERR_CERT_COMMON_NAME_INVALID’, если я пытаюсь использовать префикс ‘www’ перед моим основным/сертифицированным доменом nzarchitecture.net.nz

Ниже приведён мой полный файл 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"
  - "templates/import/mysql-dep.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:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en

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

  ## TODO: Доменное имя, на которое будет отвечать этот экземпляр Discourse
  ## Обязательно. Discourse не будет работать с чистым IP-адресом.
  DISCOURSE_HOSTNAME: nzarchitecture.net.nz

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

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

  ## TODO: SMTP-сервер, используемый для проверки новых учётных записей и отправки уведомлений
  ## SMTP-адрес, имя пользователя и пароль обязательны
  # ВНИМАНИЕ: символ '#' в пароле SMTP может вызвать проблемы!
  DISCOURSE_SMTP_ADDRESS: smtp.mailgun.org
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: ****************
  DISCOURSE_SMTP_PASSWORD: "****************"
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (опционально, по умолчанию true)

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

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

## Контейнер 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
          - git clone https://github.com/discourse/discourse-bbcode.git
## Любые пользовательские команды для запуска после сборки
run:
  - exec: echo "Начало пользовательских команд"
  ## Если вы хотите установить адрес электронной почты в поле 'От' для первой регистрации, раскомментируйте и измените:
  ## После получения первого письма регистрации закомментируйте строку обратно. Она должна выполниться только один раз.
  #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"
  - exec: echo "Конец пользовательских команд"

after_ssl:
    - replace:
        filename: /usr/local/bin/letsencrypt
        from: /-d nzarchitecture.net.nz /
        to: "-d nzarchitecture.net.nz -d www.nzarchitecture.net.nz "
        global: true

Является ли тот факт, что в /usr/local/bin/ нет файлов, частью проблемы?

Где найти правильный файл letsencrypt, чтобы поместить его туда, если это необходимо?

И, если ‘letsencrypt’ — это имя файла в конце этого пути, нормально ли, что в имени файла нет расширения?

Моя идея — удалить строку after_ssl и сдвинуть остальные строки влево.

Если вы добавите мой SSH-ключ командой

ssh-import-id-gh pfaffman

и напишете мне на почту, я посмотрю, что смогу сделать.

Спасибо! @pfaffman

Только что отправил вам письмо

Вот обновление.

Добавление этого в секцию run в нижней части вашего файла app.yml решит проблему с запросом сертификатов для DISCOURSE_HOSTNAME и www.DISCOURSE_HOSTNAME через /usr/local/bin/letsencrypt.

- exec: sed -i "s|-d \${DISCOURSE_HOSTNAME}|-d \${DISCOURSE_HOSTNAME} -d www.\${DISCOURSE_HOSTNAME}|g" /usr/local/bin/letsencrypt

Раньше этого (как-то?) хватало, но теперь, когда поступает запрос на http://www.HOSTNAME/.well-known…, он перенаправляется на сайт без www, вместо отправки необходимого вызова. Я пытался сделать что-то вроде этого:

server {
  listen 80;
  listen [::]:80;
  server_name nzarchitecture.net.nz www.nzarchitecture.net.nz;

  # Обслуживание вызова ACME (Let's Encrypt)
  location ^~ /.well-known/acme-challenge/ {
    root /var/www/discourse/public;  # Убедитесь, что это совпадает с корневым каталогом Let's Encrypt
    allow all;
  }

  # Перенаправление всего остального на HTTPS
  location / {
    return 301 https://$host$request_uri;
  }
}

но так и не смог разобраться до конца.

Если кто-то из команды это читает, было бы здорово, если бы существовал хук letsencrypt, чтобы его можно было вызывать в after_letsencrypt. До внесения этих изменений в after_ssl всё работало, но теперь, если мы делаем так, это выполняется после SSL, но до letsencrypt.

Я тоже наблюдаю эту проблему. Я дам вам знать, если смогу исправить.

Мой DISCOURSE_HOSTNAMEwww.textkit.com. Я делаю обратное по сравнению с nzarchitecture.net.nz и добавляю хост без www в свой сертификат. У меня это сработало так:

- exec: sed -i "s|-d \${DISCOURSE_HOSTNAME}|-d www.textkit.com -d textkit.com|g" /usr/local/bin/letsencrypt

Не могу сказать, почему у @pfaffman и nzarchitecture.net.nz возникли проблемы с его версией, хотя, возможно, это связано с порядком хостов в моём случае.

У меня тоже возникла эта проблема.

Я удалил это (закомментировав):

  after_ssl:
#    - replace:
#        filename: "/etc/runit/1.d/letsencrypt"
#        from: /--keylength/
#        to: "-d example.com --keylength"
#    - replace:
#        filename: "/etc/nginx/conf.d/discourse.conf"
#        from: /return 301 https.+/
#        to: |
#          return 301 https://$host$request_uri;

и добавил это в раздел run в самом низу, как рекомендовал @pfaffman:

- exec: sed -i "s|-d \${DISCOURSE_HOSTNAME}|-d \${DISCOURSE_HOSTNAME} -d www.\${DISCOURSE_HOSTNAME}|g" /usr/local/bin/letsencrypt

Этого, похоже, оказалось достаточно для меня:

  • сайт пересобрался и, судя по всему, теперь имеет валидные сертификаты
  • перенаправление с корневой доменной зоны на www работает

Спасибо @pfaffman!

О, круто! Возможно, изменение, которое они внесли и которое позволило возобновить работу сертификатов, также решило проблему, с которой я столкнулся.

Я буду иметь это в виду, если наткнусь на другие сайты, которым это понадобится, до того как PR будет принят.

Здесь несколько движущихся частей. У меня это сработало при той пересборке, я сообщу здесь, если ситуация изменится!

Пул-реквест, позволяющий обновлять сертификаты, ещё не принят — этот вопрос пока находится в ожидании.

Однако после его принятия это позволит значительно упростить файл app.yml.

Как-то странно, но этот кусок кода работает на одном из моих сайтов, а старый код (и только старый) — на другом. :man_shrugging:

Впрочем, надеюсь, что скоро всё это уже не будет иметь значения!

Это очень странно. Не зафиксирована ли версия discourse_docker на старой версии?

Нет, это не так. Между экземплярами (похожие темы / плагины / конфигурация) особой разницы нет, за исключением того, что один из них значительно старше другого.

Впрочем, надеюсь, это лишь теоретический вопрос.

Было ли это уже реализовано?

Мой сертификат LetsEncrypt истёк вчера, и автоматическое продление не сработало. Не уверен, связано ли это с предложенными изменениями в app.yml, которые я внёс согласно обсуждению выше, или с последующими обновлениями Discourse.

С помощью ИИ (да, я знаю!) мне удалось наконец продлить сертификат, пройдя с его помощью множество тупиковых путей, связанных с использованием nginx и certbot (что, в конце концов, всё-таки сработало, если честно), после чего я откатил эти изменения и вернулся к методу по умолчанию, управляемому Discourse. В процессе мне пришлось несколько раз пересобрать систему, поэтому неясно, что именно спровоцировало продление: факт пересборки или изменения в коде.

Помимо ожидания истечения текущего сертификата, есть ли способ проверить, вернётся ли продление в будущем к автоматическому режиму?

Извините за неудобства. Мне интересно: когда вы последний раз пересобирали проект до этого?

Мы только что проверили и протестировали процесс обновления, но не смогли воспроизвести проблему. Наши тесты показывают успешное обновление, поэтому либо что-то не происходит на нашей стороне, либо есть различия в том, как работает обновление в тестовой и продакшн-средах Let’s Encrypt.

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

Мы используем acme.sh под капотом (в контейнере по пути /opt/acme.sh). Если вы хотите, вы можете зайти в запущенный Docker-контейнер и запустить этот скрипт, чтобы проверить или выполнить обновление вручную.