I have investigated a bit the solution with the signal handler.
Erlang executes :init.stop() on SIGTERM by default. That means that a simple kill PID shuts the thing down orderly.
However, you need to figure out the PID of the process, and open a different terminal to execute the command. Besides, custom code passed to System.at_exit/1 is not invoked by the event handler.
Ctrl-C sends SIGINT. The only shortcut that I’ve seen sends a different signal is Ctrl-\, which sends SIGQUIT. So, I am using this event handler:
defmodule QuitScript do
@behaviour :gen_event
def init(_), do: {:ok, nil}
def handle_event(:sigquit, _state), do: :remove_handler
def handle_event(signal, state), do: :erl_signal_handler.handle_event(signal, state)
def handle_call(_, state), do: {:ok, :ok, state}
def terminate(_reason, _state) do
IO.puts("My exit code")
System.stop()
end
end
:gen_event.swap_handler(:erl_signal_server, {:erl_signal_handler, []}, {QuitScript, []})
Notice that the terminate/2 callback allows you to run custom code on exit.
If you throw that to a script and execute it for example like this
mix run --no-halt foo.exs
Ctrl-C still behaves as always, and additionally Ctrl-\ shuts the thing down orderly (you only see ^\ echoed in the terminal, not too bad).