Last active
July 22, 2024 07:56
-
-
Save parndt/5e1dd05afff91f2fe0b8d2a08e432c82 to your computer and use it in GitHub Desktop.
Proxy a Ruby app from an Elixir app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule App.Application do | |
# See https://hexdocs.pm/elixir/Application.html | |
# for more information on OTP Applications | |
@moduledoc false | |
use Application | |
@impl true | |
def start(_type, _args) do | |
children = | |
ecto_repos(App.config_env()) ++ | |
[ | |
# Start the PubSub system | |
{Phoenix.PubSub, name: App.PubSub} | |
# Start a worker by calling: App.Worker.start_link(arg) | |
# {App.Worker, arg} | |
] ++ | |
extra_children( | |
App.config_env(), | |
(System.get_env("API_PROD_ENDPOINT") || "") |> String.split(".") |> List.last() | |
) | |
Supervisor.start_link(children, strategy: :one_for_one, name: App.Supervisor) | |
end | |
def ecto_repos(:test), do: [App.Repo] | |
def ecto_repos(_), do: [App.Repo, App.Repo.Replica] | |
defp extra_children(:dev, _), do: all_extra_children() | |
defp extra_children(:prod, "localhost"), do: all_extra_children() | |
defp extra_children(_, _), do: [] | |
defp all_extra_children do | |
[ | |
Supervisor.child_spec( | |
{App.Ports.Commander, | |
[ | |
command: "../bin/monitor.sh ./node_modules/.bin/ts-node ./scripts/build development", | |
args: [ | |
{:cd, "web"}, | |
{:env, [{'TS_NODE_PROJECT', 'tsconfig.webpack.json'}, {'PORT', '8080'}]} | |
] | |
]}, | |
id: :build_assets | |
), | |
Supervisor.child_spec( | |
{App.Ports.Commander, | |
[ | |
command: "../bin/monitor.sh ./node_modules/.bin/serve -C ./tmp", | |
args: [ | |
{:cd, "web"}, | |
{:env, [{'PORT', '8080'}]} | |
] | |
]}, | |
id: :assets | |
), | |
Supervisor.child_spec( | |
{App.Ports.Commander, | |
[ | |
command: "bin/monitor.sh caddy run --watch" | |
]}, | |
id: :caddy | |
), | |
Supervisor.child_spec( | |
{App.Ports.Commander, | |
[ | |
args: [{:cd, "web"}], | |
command: "../bin/monitor.sh bin/puma" | |
]}, | |
id: :puma | |
) | |
] | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.localhost { | |
reverse_proxy http://127.0.0.1:9393 | |
tls ./app.localhost+5.pem ./app.localhost+5-key.pem | |
} | |
admin.app.localhost { | |
reverse_proxy http://127.0.0.1:9292 | |
} | |
api.app.localhost { | |
reverse_proxy http://127.0.0.1:3456 | |
} | |
assets.app.localhost { | |
reverse_proxy http://127.0.0.1:8080 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# lib/app/ports/commander.ex | |
defmodule App.Ports.Commander do | |
use GenServer | |
require Logger | |
# GenServer API | |
def start_link(args \\ [], opts \\ []) do | |
GenServer.start_link(__MODULE__, args, opts) | |
end | |
def init(args \\ []) do | |
Process.flag(:trap_exit, true) | |
port = Port.open({:spawn, args[:command]}, [:binary, :exit_status] ++ (args[:args] || [])) | |
Port.monitor(port) | |
{:ok, %{port: port, latest_output: nil, exit_status: nil}} | |
end | |
# This callback handles data incoming from the command's STDOUT | |
def handle_info({_port, {:data, text_line}}, state) do | |
Logger.info("Latest output: #{inspect(text_line)}") | |
{:noreply, %{state | latest_output: String.trim(text_line)}} | |
end | |
# This callback tells us when the process exits | |
def handle_info({_port, {:exit_status, status}}, state) do | |
Logger.info("External exit: :exit_status: #{status}") | |
{:noreply, %{state | exit_status: status}} | |
end | |
def handle_info({:DOWN, _ref, :port, port, :normal}, state) do | |
Logger.info("Handled :DOWN message from port: #{inspect(port)}") | |
{:noreply, state} | |
end | |
# no-op catch-all callback for unhandled messages | |
def handle_info(msg, state) do | |
Logger.info("Unhandled message: #{inspect(msg)}") | |
{:noreply, state} | |
end | |
def terminate(reason, %{port: port} = state) do | |
Logger.info( | |
"** TERMINATE: #{inspect(reason)}. This is the last chance to clean up after this process." | |
) | |
Logger.info("Final state: #{inspect(state)}") | |
port_info = Port.info(port) | |
os_pid = port_info[:os_pid] | |
Logger.warn("Orphaned OS process: #{os_pid}") | |
:normal | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# bin/monitor.sh | |
#!/usr/bin/env bash | |
# Start the program in the background | |
exec "$@" & | |
pid1=$! | |
# Silence warnings from here on | |
exec >/dev/null 2>&1 | |
# Read from stdin in the background and | |
# kill running program when stdin closes | |
exec 0<&0 $( | |
while read; do :; done | |
kill -KILL $pid1 | |
) & | |
pid2=$! | |
# Clean up | |
wait $pid1 | |
ret=$? | |
kill -KILL $pid2 | |
exit $ret |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment