discourse/bin/ember-cli
Loïc Guitaut 6b243ffdfd
DEV: Remove Unicorn web server in favor of Pitchfork (#39032)
Pitchfork has been the default web server for some time now. This
removes Unicorn entirely to simplify the codebase and unblock future
improvements (like Rack 3).

Notable changes beyond the straightforward removal:

- `Discourse.after_unicorn_worker_fork` →
`Discourse.apply_worker_db_variables_overrides`: renamed and wired into
pitchfork.conf.rb's `after_worker_fork`. This actually *fixes*
per-worker DB variable overrides (`unicorn_worker_db_variables_*`) which
were never called under Pitchfork.
- `bin/ember-cli`: `--unicorn` flag renamed to `--server` (`-u` kept).
- `lib/demon/sidekiq.rb`: removed Unicorn-specific USR1/USR2 signal
handlers and `reopen_logs` (called `Unicorn::Util.reopen_logs`), which
were already dead code under Pitchfork.

Intentionally kept unchanged:
- `config/unicorn_launcher` (used by Docker images, separate effort)
- `docker_manager` plugin (separate repo)
- `UNICORN_*` env vars (renaming deferred)
- Rack < 3 constraint (separate PR)
2026-04-01 15:04:59 +02:00

160 lines
4.3 KiB
Ruby
Executable file

#!/usr/bin/env ruby
# frozen_string_literal: true
require "pathname"
require "open3"
RAILS_ROOT = File.expand_path("../../", Pathname.new(__FILE__).realpath)
PORT = ENV["UNICORN_PORT"] ||= "3000"
HOSTNAME = ENV["DISCOURSE_HOSTNAME"] ||= "127.0.0.1"
CUSTOM_ARGS = %w[--try --test --build --server --unicorn -u --forward-host]
PROXY =
if ARGV.include?("--try")
"https://try.discourse.org"
else
"http://#{HOSTNAME}:#{PORT}"
end
def process_running?(pid)
!!Process.kill(0, pid)
rescue Errno::ESRCH
false
end
command =
if ARGV.include?("--test")
"test"
elsif ARGV.include?("--build")
"build"
else
"server"
end
class String
def cyan
"\e[36m#{self}\e[0m"
end
def red
"\033[31m#{self}\e[0m"
end
end
if ARGV.include?("-h") || ARGV.include?("--help")
puts "ember-cli OPTIONS"
puts "#{"--try".cyan} To proxy try.discourse.org"
puts "#{"--test".cyan} To run the test suite"
puts "#{"--server, -u".cyan} To run a server as well"
puts "The rest of the arguments are passed to ember server per:", ""
exec "pnpm ember #{command} --help"
end
args = ["--dir=frontend/discourse", "ember", command] + (ARGV - CUSTOM_ARGS)
if !args.include?("test") && !args.include?("build") && !args.include?("--proxy")
args << "--proxy"
args << PROXY
end
node_modules_outdated =
begin
File.read("node_modules/.pnpm/lock.yaml") != File.read("pnpm-lock.yaml")
rescue Errno::ENOENT
true
end
if node_modules_outdated
puts "[bin/ember-cli] Detected outdated or missing node_modules. Running pnpm install..."
if !system "pnpm", "--dir=#{RAILS_ROOT}", "install"
if !system("command -v pnpm >/dev/null;")
abort "pnpm is not installed. run `npm install -g pnpm`"
end
exit 1
end
end
pnpm_env = {
"TERM" => "dumb", # simple output from ember-cli, so we can parse/forward it more easily
}
pnpm_env["FORWARD_HOST"] = "true" if ARGV.include?("--forward-host")
if ARGV.include?("-u") || ARGV.include?("--server") || ARGV.include?("--unicorn")
server_env = { "DISCOURSE_PORT" => ENV["DISCOURSE_PORT"] || "4200" }
if command == "server" && ENV["CODESPACE_NAME"]
server_env.merge!(
{
"DISCOURSE_PORT" => "443",
"DISCOURSE_FORCE_HTTPS" => "1",
"DISCOURSE_FORCE_HOSTNAME" =>
"#{ENV["CODESPACE_NAME"]}-4200.#{ENV["GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN"]}",
},
)
end
server_pid = spawn(server_env, "#{__dir__}/pitchfork")
ember_cli_pid = nil
sigint_received = false
Thread.new do
Open3.popen2e(pnpm_env, "pnpm", *args.to_a.flatten) do |i, oe, t|
ember_cli_pid = t.pid
puts "Ember CLI running on PID: #{ember_cli_pid}"
oe.each do |line|
if line.include?("\e[32m200\e") || line.include?("\e[36m304\e[0m") ||
line.include?("POST /message-bus")
# skip 200s and 304s and message bus
else
puts "[ember-cli] #{line}"
end
end
end
# Only send TERM to the server if ember exited on its own (not from
# Ctrl+C). When the user presses Ctrl+C, all processes in the foreground
# group already received INT directly from the terminal. Sending an
# additional TERM races with the server's hard shutdown and can cause it
# to fall back to a slow graceful shutdown, making the process appear
# stuck for up to 60 seconds.
if !sigint_received && process_running?(server_pid)
puts "[bin/ember-cli] ember-cli process stopped. Terminating server."
Process.kill("TERM", server_pid)
end
end
int_count = 0
trap("INT") do
sigint_received = true
int_count += 1
case int_count
when 1
# Swallowed to give children time to handle it
when 2
puts "\n[bin/ember-cli] Interrupt again to force shutdown..."
else
puts "\n[bin/ember-cli] Forcing shutdown..."
begin
Process.kill("KILL", server_pid)
rescue StandardError
nil
end
if ember_cli_pid
begin
Process.kill("KILL", ember_cli_pid)
rescue StandardError
nil
end
end
exit!(1)
end
end
Process.wait(server_pid)
if ember_cli_pid && process_running?(ember_cli_pid)
puts "[bin/ember-cli] server process stopped. Terminating ember-cli."
Process.kill("TERM", ember_cli_pid)
end
else
exec(pnpm_env, "pnpm", *args.to_a.flatten)
end