Docker 없이 Discourse 배포하기

해결책에 대한 몇 가지 세부 사항:

  1. Nginx 설정:
    리버스 프록시(haproxy) 뒤에 있을 경우, /var/discourse/config/nginx-config-sample.conf를 /etc/nginx/sites-enabled/discourse.conf 또는 유사한 대상 경로로 복사한 후, 필요에 따라 listen <yourreverseproxyport>를 수정하고 server_name의 _를 호스트명(예: subdmain.example.com)으로 대체하면 됩니다. 그 외에는 변경할 것이 없습니다.
  2. 페이지가 표시되지 않음(“Oops” 메시지) 및 이메일 관리자 등록이 작동하지 않는 문제:
    최초 부팅 시 관리자 등록 페이지 이후 페이지가 표시되지 않는 원인은 Magick였습니다. 로그에 표시된 파일 /var/www/discourse/lib/letter_avatar.rb의 112번째 줄을 확인해 보니, 두 개의 magick 명령어가 응답하지 않는 것으로 나타났습니다. convert는 응답했으므로 해당 줄의 magick을 convert로 대체했습니다. 이 수정 후 또 다른 오류가 기록되었습니다. 파일에서 제공된 명령어와 동일한 절차로 시도해 보았으나 magick 명령어는 작동하지 않았고 convert도 실패했습니다. 참고: magick --version은 7.x였고, convert --version은 6.x였습니다. 원인은 먼저 apt로 imagemagick을 설치한 후 소스에서 magick 7을 설치했기 때문에 발생한 충돌이었습니다. magick이 convert 명령어가 오래되었다고 알려주었습니다. 그래서 magick 7만 사용하여 스크립트를 다시 실행했습니다. 이렇게 하자마자 문제가 즉시 해결되었고, 며칠간 기다렸던 새 페이지를 볼 수 있게 되었으며 이메일도 정상 작동했습니다. Magick은 정말 마법과 같습니다.
  3. 혼합 콘텐츠(mixed content) 문제는 여전히 남아 있지만, 웹사이트는 정상적으로 작동합니다.
    참고: puma.rb에서 bind ENV.fetch("PUMA_BIND", "tcp://#{ENV['PUMA_BIND_ALL'] ? '' : '127.0.0.1:'}3000") 줄은 이 형태여야 합니다(첫 번째 게시물의 수정 사항).
  4. 3번 항목에 대한 수정, discourse의 https 강제 적용에 관하여: 정확한 이유는 알 수 없으나, 이제 브라우저에서 ‘혼합 콘텐츠’ 경고가 더 이상 표시되지 않습니다(캐시 업데이트 때문일 수도 있습니다). 따라서 지금은 모든 것이 정상입니다. 이제 스크립트를 마무리하기만 하면 됩니다.
3개의 좋아요

동작 확인 완료!!!
ImageMagick v7이 Ooops 문제를 해결합니다!
지금까지 테스트해 본 결과, 완전히 정상적으로 작동하고 있습니다.
나머지 기능도 테스트한 후 최대한 빨리 보고드리겠습니다.

1개의 좋아요

테스트를 위해 제가 한 일은 puma를 실행할 때 환경 변수 PUMA_BIND를 설정하는 것이었습니다.

ImageMagick에 관해서는, 웹 UI에서 큰 이미지를 업로드할 때 이미지 변환을 실행하는 데 실패하는 문제가 발생하고 있습니다. 왜 그런지는 모르겠지만, 시스템은 변환을 거부하고 있습니다… 아직 문제를 디버깅 중입니다.

디버그 관련해서 진전이 있나요? 제 쪽에서는 Magick 작업을 진행 중입니다.
약 50KB 크기의 이미지에서 브라우저에 팝업이 표시되며 다음과 같은 내용이 나옵니다:
timeout -k 40.0 20 magick gif:/tmp/RackMultipart20250927-23598-xrrp6e.gif -auto-orient -background white -interlace none -flatten -debug all -quality 90 jpg:/tmp/image20250927-23598-9ujq3d.jpg
그런데 이미지는 로드되지 않습니다.
이미지 크기가 더 크면 팝업은 뜨지 않지만, 로딩 휠이 무한정 회전하며 결과가 나오지 않습니다. /var/www/discourse/log 에는 오류가 기록되지 않습니다.

나도 같은 문제야 :joy::sob:

brew 저장소에서 imagemagick을 사용해야 겨우 실행을 시킬 수 있었어.

근데 이미지가 3MB를 넘으면 실패하더라… 아마 내가 policy 설정을 너무 엄격하게 잡은 것 같아.

한번 시도해 봐!!!

docker 설치로 테스트 중인데, nginx, unicorn, redis, postgresql 그 외 나머지를 전부 단 하나의 컨테이너에 배포하는 건 좀 멍청한 것 같아… 전혀 합리적이지 못해. 그리고 대규모 배포를 위한 인프라 문서도 없거든. IT 분야에서 20년 넘게 일해 봤는데, 이런 인프라를 쓰면 가까운 미래에 문제가 생길 것 같아.

“공간을 먹는 docker”( Dormammu 같은) 이야기는 안 해도 되지만 :rofl::rofl:

아래 끝에 설명된 해결책으로 이 문제가 해결되었습니다:

디버그 1: bundle db:create 출력, 첫 시작 시 로그에도 표시됨:

…
unknown OID 21096: failed to recognize type of ‘embeddings’. It will be treated as String.
pngquant worker: pngquant not found; please provide proper binary or disable this worker (–no-pngquant argument or :pngquant => false through options)
oxipng worker: oxipng not found; please provide proper binary or disable this worker (–no-oxipng argument or :oxipng => false through options)
jhead worker: jhead not found, jpegtran not found; please provide proper binary or disable this worker (–no-jhead argument or :jhead => false through options)
jpegoptim worker: jpegoptim not found; please provide proper binary or disable this worker (–no-jpegoptim argument or :jpegoptim => false through options)
…

디버그 2: 일부 magick 명령의 파일 출력:

no decode delegate for this image format

이 마지막 문제는 여기에서 언급되고 있습니다.

해결책은 여기에 있습니다 (포맷 플러그인을 포함하여 magick 설치):

t=$(mktemp) && \
wget 'https://dist.1-2.dev/imei.sh' -qO "$t" && \
bash "$t" && \
rm "$t"

이 작업을 수행한 후, 업로드 크기가 최대 518KB까지 가능합니다. 그 이상은 불가능합니다. 이는 이미지에만 해당됩니다. 기타 모든 문서, 오디오, 비디오 업로드는 정상적으로 작동합니다.

나머지 문제에 대한 임시 해결책:
관리자 설정, discourse.conf, nginx/sites-enables/discourse.conf, git 폴더를 확인했습니다. 마침 AdminPanel/Parameters/Files에서 "Composer media optimization image enabled"를 비활성화한 후 모든 것이 정상적으로 작동합니다. 이제 어떤 이미지도 업로드할 수 있습니다.

네, IMEI는 거의 같은 해결책이지만, 디스크 공간을 약 2GB 정도 차지하지는 않을 거라고 확신합니다 :rofl::rofl::sob::sob::sob::face_with_symbols_on_mouth::face_with_symbols_on_mouth::face_with_symbols_on_mouth:

업로드 시 최대 파일 크기 설정에 대한 당신의 해결책을 확인해 보겠습니다.

또한 이메일을 “무료로” 보내는 방법에 대해서도 고심 중입니다 (초기 단계라 mailtrap/mailgun 등을 계약하고 싶지 않거든요…)

메일 서버를 설치해 보지 않겠어요? 저는 직접 설치 스크립트를 작성한 메일 서버를 가지고 있습니다. 관심이 있다면 말씀해 주세요. 도메인 이름에 대해 연회비만 내면 됩니다.

물론이죠! 설명이나 git 저장소를 DM으로 보내주세요! :grinning_face_with_smiling_eyes:

몇 년 전부터 자체 호스팅 메일 서버를 사용하지 않고 있습니다…

rake assets:precompile를 실행하면 No such file or directory - brotli라는 오류가 표시됩니다. 패키지 관리자를 사용하여 설치하세요.

2개의 좋아요

다음과 같은 오류가 발생하고 있습니다:

bundler: failed to load command: puma (/home/mry/.rbenv/versions/3.4.6/bin/puma)
/home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/cluster.rb:472:in 'Puma::Cluster#run': undefined method 'wait_readable' for nil (NoMethodError)

            if read.wait_readable([0, @next_check - Time.now].max)
                   ^^^^^^^^^^^^^^
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/launcher.rb:202:in 'Puma::Launcher#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/lib/puma/cli.rb:73:in 'Puma::CLI#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/puma-7.0.4/bin/puma:10:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/bin/puma:25:in 'Kernel#load'
        from /home/mry/.rbenv/versions/3.4.6/bin/puma:25:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Kernel.load'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Bundler::CLI::Exec#kernel_load'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:452:in 'Bundler::CLI#exec'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/exe/bundle:28:in 'block in <top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/lib/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
        from /home/mry/.rbenv/versions/3.4.6/lib/ruby/gems/3.4.0/gems/bundler-2.6.4/exe/bundle:20:in '<top (required)>'
        from /home/mry/.rbenv/versions/3.4.6/bin/bundle:25:in 'Kernel#load'
        from /home/mry/.rbenv/versions/3.4.6/bin/bundle:25:in '<main>'

그 후 Puma가 스스로 재시작됩니다. 이를 방지하려면 어떻게 해야 할까요?

수정: 이는 Ruby 3.4.0부터 Puma에 속하는 문제인 것 같습니다. YJIT를 비활성화하여 해결되었습니다.

그럼 3.3.7을 사용하세요. 제가 현재 사용하고 있는 버전입니다.

게임을 다시 다운로드할 게으름이 나서, 3.4.6을 계속 사용하면서 YJIT만 비활성화했습니다.

안녕하세요,
Docker 없이 Debian 12 컨테이너(제 경우 LXC)에서 3단계로 전체 설치를 수행하는 스크립트를 공유합니다.
if 블록을 사용하여 필요한 부분만 실행하도록 하세요.

  1. 매개변수를 설정하고 필요한 인자와 함께 root 권한으로 실행하세요. 주의: 스크립트는 “### MANUAL” 부분 앞의 종료 지점에서 종료됩니다.
  2. 이 명령들은 컨테이너 터미널에서 수동으로 입력해야 합니다. 제 LXC 비특권 컨테이너에서는 성공하지 못했습니다. (기본 사용자는 root입니다)
  3. 나머지 부분을 실행하세요.
    여러 번 테스트하여 작동하는 것을 확인했지만, 공개용으로 수정한 후 일부 버그가 있을 수 있습니다.
install_script
#!/bin/bash
########## DOCKER 없이 DEBIAN 12에 DISCOURSE 설치 스크립트 ##################
#- 2025년 12월에 마지막으로 테스트
############################## ROOT 권한으로 실행, LXC 컨테이너에서 실행하는 것이 가장 좋음!!!!!!!
#- 토론 출처 : https://meta.discourse.org/t/deploy-discourse-without-docker/351194
##############################################################################
# 수동 매개변수
certbot_contact_email=hello@domain.tld
certbot_renew_port=9785
nginx_discourse_port=8080
DISCOURSE_DEVELOPER_EMAILS='emailaddress'
DISCOURSE_SMTP_ADDRESS=smtp_server_address
DISCOURSE_SMTP_PORT=465
DISCOURSE_SMTP_USER_NAME=xxx
DISCOURSE_SMTP_PASSWORD=xxx
DISCOURSE_SMTP_DOMAIN=xxx
DISCOURSE_NOTIFICATION_EMAIL=no-reply-xxxx
DISCOURSE_MAXMIND_ACCOUNT_ID=50
# 수동 매개변수 끝

shopt -s expand_aliases
source /etc/bash.bashrc
container=$1
cont_haproxy=$2
pool=$3
lxc_image=$4
hostname=$5
db_password=$6
maxmind_license_key=$7
DISCOURSE_HOSTNAME=$hostname
DISCOURSE_MAXMIND_LICENSE_KEY=$maxmind_license_key

### MAGICK 7 및 플러그인 설치
# https://github.com/SoftCreatR/imei/?tab=readme-ov-file
if [ 1 == 1 ]; then
t=$(mktemp) && \
wget 'https://dist.1-2.dev/imei.sh' -qO "$t" && \
bash "$t" && \
rm "$t"
fi

### 기본 패키지 설치 ###
if [ 1 == 1 ]; then
apt install -y apt-utils postgresql nginx libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static redis
systemctl enable --now postgresql redis-server nginx
useradd -m -s /bin/bash discourse
usermod -aG sudo discourse
passwd -d discourse
# RVM (RUBY)
apt install -y ruby-full # 필수
su - discourse -c 'curl -sSL https://get.rvm.io | bash'
su - discourse -c 'source /home/discourse/.bashrc'
su - discourse -c 'source /home/discourse/.rvm/scripts/rvm' # rvm 설치 후 필수
su - discourse -c 'rvm install 3.3.7' # 현재 기준으로 node 3.3.7 설치
source /home/discourse/.rvm/scripts/rvm
# fi
# POSTGRESQL 설정
apt update && apt upgrade -y
apt autoremove -y

# 업그레이드를 수락해야 합니다
/usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
apt install -y postgresql-18-pgvector
fi
alias mydb="sudo -u postgres psql discourse -c "
if [ 1 == 1 ]; then
su - discourse -c 'sudo -u postgres createuser -s discourse'
# fi
su - discourse -c 'sudo -u postgres createdb discourse'
su - discourse -c 'sudo -u postgres psql -c "ALTER USER discourse WITH ENCRYPTED PASSWORD '\''password'\'';"'
alias mydb="sudo -Hiu discourse psql -c "
su - discourse -c 'psql -c "GRANT CONNECT ON DATABASE discourse TO root;"'
su - discourse -c 'psql -c "CREATE EXTENSION pg_trgm;CREATE EXTENSION hstore;"'
su - discourse -c 'psql -c "ALTER DATABASE discourse OWNER TO discourse;"'
su - discourse -c 'psql -c "CREATE EXTENSION unaccent;CREATE EXTENSION plpgsql;"'
su - discourse -c 'psql -c "CREATE EXTENSION vector;"'
echo "OK"

### GITHUB에서 DISCOURSE 설치
cd /var/www/
git clone https://github.com/discourse/discourse
git config --global --add safe.directory /var/www/discourse
# exit
### bundle, ruby 등에 필수, discourse 자체에는 불필요
tee -a /home/discourse/.bashrc > /dev/null <<EOT
export RAILS_ENV=production
export UNICORN_SIDEKIQ_MAX_RSS=800
export UNICORN_WORKERS=8
export UNICORN_SIDEKIQS=1
export PUMA_SIDEKIQ_MAX_RSS=800
export PUMA_WORKERS=8
export PUMA_SIDEKIQS=1
export RUBY_GC_HEAP_GROWTH_MAX_SLOTS=40000
export RUBY_GC_HEAP_INIT_SLOTS=400000
export RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.5

export DISCOURSE_HOSTNAME=$hostname
export DISCOURSE_DEVELOPER_EMAILS=$DISCOURSE_DEVELOPER_EMAILS
export DISCOURSE_SMTP_ADDRESS=$DISCOURSE_SMTP_ADDRESS
export DISCOURSE_SMTP_PORT=$DISCOURSE_SMTP_PORT
export DISCOURSE_SMTP_USER_NAME=$DISCOURSE_SMTP_USER_NAME
export DISCOURSE_SMTP_PASSWORD=$DISCOURSE_SMTP_PASSWORD
export DISCOURSE_SMTP_DOMAIN=$DISCOURSE_SMTP_DOMAIN
export DISCOURSE_NOTIFICATION_EMAIL=$DISCOURSE_NOTIFICATION_EMAIL
export DISCOURSE_MAXMIND_ACCOUNT_ID=$DISCOURSE_MAXMIND_ACCOUNT_ID
export DISCOURSE_MAXMIND_LICENSE_KEY=$maxmind_license_key

export DISCOURSE_ENABLE_CORS=true
export DISCOURSE_MAX_REQS_PER_IP_MODE=none
export DISCOURSE_MAX_REQS_PER_IP_PER_MINUTE=20000
export DISCOURSE_MAX_REQS_PER_IP_PER_10_SECONDS=5000
export DISCOURSE_MAX_ASSET_REQS_PER_IP_PER_10_SECONDS=20000
export DISCOURSE_MAX_REQS_RATE_LIMIT_ON_PRIVATE=false
export DISCOURSE_MAX_USER_API_REQS_PER_MINUTE=200
export DISCOURSE_MAX_USER_API_REQS_PER_DAY=28800
export DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE=600
export DISCOURSE_MAX_DATA_EXPLORER_API_REQ_MODE=none
EOT
cd /var/www/discourse
git stash
git pull
git checkout tests-passed 
cd plugins
for plugin in *
do
echo "ok"
    echo $plugin; cd ${plugin}; git pull; cd ..
done
# fi
chown -R discourse:discourse /var/www/discourse/     
mkdir -p /var/www/discourse/public
chown -R discourse:www-data /var/www/discourse/public
tee -a /var/www/discourse/config/discourse.conf > /dev/null <<EOT
max_data_explorer_api_req_mode = 'none'
max_user_api_reqs_per_day = '28800'
hostname = '$hostname'
redis_host = 'localhost'
smtp_user_name = $$DISCOURSE_SMTP_USER_NAME
db_password = '$db_password'
smtp_address = '$DISCOURSE_SMTP_ADDRESS'
db_socket = ''
max_reqs_per_ip_per_10_seconds = '5000'
max_asset_reqs_per_ip_per_10_seconds = '20000'
max_reqs_rate_limit_on_private = 'false'
developer_emails = '$DISCOURSE_DEVELOPER_EMAILS'
max_user_api_reqs_per_minute = '200'
maxmind_license_key = '$maxmind_license_key'
smtp_port = '$DISCOURSE_SMTP_PORT'
maxmind_account_id = '$DISCOURSE_MAXMIND_ACCOUNT_ID'
smtp_password = '$DISCOURSE_SMTP_PASSWORD'
max_reqs_per_ip_per_minute = '20000'
notification_email = '$DISCOURSE_NOTIFICATION_EMAIL'
db_host = 'localhost'
enable_cors = 'true'
db_port = ''
max_reqs_per_ip_mode = 'none'
smtp_domain = '$DISCOURSE_SMTP_DOMAIN'
#max_admin_api_reqs_per_minute = '600'
smtp_force_tls='true'
EOT

cd /var/www/discourse && sed -i '/gem "rails_multisite"/i gem "rails"' Gemfile
cd /var/www/discourse && sed -i '/gem "image_optim"/i gem "image_optim_pack"' Gemfile
su - discourse -c 'cd /var/www/discourse && bundle install'
# fi

### PNPM (NODEJS)
su - discourse -c 'curl -fsSL https://get.pnpm.io/install.sh' | su - discourse -c 'sh -'
su - discourse -c 'source /home/discourse/.bashrc'
source /home/discourse/.rvm/scripts/rvm
su - discourse -c 'sed -i "s/~> 3.3/3.3.7/" /var/www/discourse/Gemfile'
chown -R discourse:discourse /var/www/discourse/Gemfile Gemfile.lock
apt-get -yqq install brotli # 이것이 필요한가요 ????????????????
# fi
su - discourse -c 'cd /var/www/discourse && bin/rails db:create'
# fi
su - discourse -c 'source /home/discourse/.rvm/scripts/rvm'
tee -a .bashrc > /dev/null <<EOT
# pnpm
export PNPM_HOME="/home/discourse/.local/share/pnpm"
case ":\$PATH:" in
  *":\$PNPM_HOME:"*) ;;
  *) export PATH="\$PNPM_HOME:\$PATH" ;;
esac
# pnpm 끝
EOT
which pnpm
source .bashrc
pnpm env use --global lts # 현재 기준으로 node 22 설치
cd /var/www/discourse && pnpm i
chown -R discourse:discourse .
chown -R discourse:www-data public
exit

#################################
######### 수동 작업 ################
export PATH="$PATH:/home/discourse/.rvm/bin"
rvm install "ruby-3.3.7" # root로 설치
cd /var/www/discourse
bundle install
bundle exec rake db:migrate
bundle exec rake themes:update
bundle exec rake assets:precompile
######### 수동 작업 끝 #################
##################################

fi
### puma.rb
#cd /var/www/discourse
cp /var/www/discourse/config/puma.rb /var/www/discourse/config/puma.rb_original

tee /var/www/discourse/config/puma.rb > /dev/null <<EOT
# frozen_string_literal: true

require "fileutils"

discourse_path = File.expand_path(File.expand_path(File.dirname(__FILE__)) + "/../")

enable_logstash_logger = ENV["ENABLE_LOGSTASH_LOGGER"] == "1"
puma_stderr_path = "#{discourse_path}/log/puma.stderr.log"
puma_stdout_path = "#{discourse_path}/log/puma.stdout.log"

# 활성화된 경우 logstash 로더 로드
if enable_logstash_logger
  require_relative "../lib/discourse_logstash_logger"
  FileUtils.touch(puma_stderr_path) if !File.exist?(puma_stderr_path)
  # 참고: Puma에 대해 로거 초기화를 조정해야 할 수 있습니다
  log_formatter = proc do |severity, time, progname, msg|
    event = {
      "@timestamp" => Time.now.utc,
      "message" => msg,
      "severity" => severity,
      "type" => "puma"
    }
    "#{event.to_json}\n"
  end
else
  stdout_redirect puma_stdout_path, puma_stderr_path, true
end

# 워커 수 (프로세스)
workers ENV.fetch("PUMA_WORKERS", 8).to_i

# 디렉터리 설정
directory discourse_path

# 지정된 주소와 포트에 바인딩
bind ENV.fetch("PUMA_BIND", "tcp://#{ENV['PUMA_BIND_ALL'] ? '' : '127.0.0.1:'}3000")

# PID 파일 위치
FileUtils.mkdir_p("#{discourse_path}/tmp/pids")
pidfile ENV.fetch("PUMA_PID_PATH", "#{discourse_path}/tmp/pids/puma.pid")

# 상태 파일 - pumactl에 사용
state_path "#{discourse_path}/tmp/pids/puma.state"

# 환경별 구성
if ENV["RAILS_ENV"] == "production"
  # 프로덕션 타임아웃
  worker_timeout 30
else
  # 개발 타임아웃
  worker_timeout ENV.fetch("PUMA_TIMEOUT", 60).to_i
end

# 애플리케이션 미리 로드
preload_app!

# 워커 부팅 및 종료 처리
before_fork do
  Discourse.preload_rails!
  Discourse.before_fork

  # 슈퍼바이저 확인
  supervisor_pid = ENV["PUMA_SUPERVISOR_PID"].to_i
  if supervisor_pid > 0
    Thread.new do
      loop do
        unless File.exist?("/proc/#{supervisor_pid}")
          puts "Kill self supervisor is gone"
          Process.kill "TERM", Process.pid
        end
        sleep 2
      end
    end
  end

  # Sidekiq 워커
  sidekiqs = ENV["PUMA_SIDEKIQS"].to_i
  if sidekiqs > 0
    puts "starting #{sidekiqs} supervised sidekiqs"

    require "demon/sidekiq"
    Demon::Sidekiq.after_fork { DiscourseEvent.trigger(:sidekiq_fork_started) }
    Demon::Sidekiq.start(sidekiqs)

    if Discourse.enable_sidekiq_logging?
      Signal.trap("USR1") do
        # Sidekiq 로그 재개 지연
        sleep 1
        Demon::Sidekiq.kill("USR2")
      end
    end
  end

  # 이메일 동기화 데몬
  if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true"
    puts "starting up EmailSync demon"
    Demon::EmailSync.start(1)
  end

  # 플러그인 데몬
  DiscoursePluginRegistry.demon_processes.each do |demon_class|
    puts "starting #{demon_class.prefix} demon"
    demon_class.start(1)
  end

  # 데몬 모니터링 스레드
  Thread.new do
    loop do
      begin
        sleep 60

        if sidekiqs > 0
          Demon::Sidekiq.ensure_running
          Demon::Sidekiq.heartbeat_check
          Demon::Sidekiq.rss_memory_check
        end

        if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true"
          Demon::EmailSync.ensure_running
          Demon::EmailSync.check_email_sync_heartbeat
        end

        DiscoursePluginRegistry.demon_processes.each(&:ensure_running)
      rescue => e
        Rails.logger.warn("Error in demon processes heartbeat check: #{e}\n#{e.backtrace.join("\n")}")
      end
    end
  end

  # Redis 연결 닫기
  Discourse.redis.close
end

on_worker_boot do
  DiscourseEvent.trigger(:web_fork_started)
  Discourse.after_fork
end

# 워커 타임아웃 처리
worker_timeout 30

# 저수준 워커 옵션
threads 8, 32
EOT

### CERTBOT
apt install -y certbot
certbot certonly --non-interactive --agree-tos --renew-with-new-domains  --standalone --email $certbot_contact_email --rsa-key-size 4096 --http-01-port $certbot_renew_port -d $hostname
# fi

### NGINX
cp /var/www/discourse/config/nginx.sample.conf /etc/nginx/sites-enabled/discourse.conf
sed -i "s/80/$nginx_discourse_port/" /etc/nginx/sites-enabled/discourse.conf
sed -i "s/server_name _/server_name $hostname/" /etc/nginx/sites-enabled/discourse.conf
mkdir -p /var/nginx/cache
systemctl enable nginx
systemctl restart nginx
systemctl status nginx

### SYSTEMD 서비스 자동 시작
sudo tee /etc/systemd/system/discourse.service > /dev/null <<EOT
[Unit]
Description=Discourse with Puma Server
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=discourse
Group=discourse
WorkingDirectory=/var/www/discourse
# 이 서비스가 실행되기 전에 `rvm 3.3.7 --default` 실행 필요
ExecStart=/usr/bin/bash -lc '/home/discourse/.rvm/gems/ruby-3.3.7/bin/puma -C config/puma.rb'
ExecReload=/usr/bin/bash -lc '/home/discourse/.rvm/gems/ruby-3.3.7/bin/pumactl restart'
# 아래 줄이 필요합니다
Environment=RAILS_ENV=production
# 재시작 구성
Restart=always
RestartSec=5s

# 기본 보안 조치
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=read-only

[Install]
WantedBy=multi-user.target
EOT

systemctl enable discourse
systemctl start discourse

# 또는 수동 시작
# puma -C config/puma.rb

### 설정
echo "LAST MANUAL SETTINGS:"
echo ""
echo "GO in Admin>files and add wanted file extensions - vidéos, audio et documents
echo "TO FORCE HTTPS: https://domain.com/admin/config/security"