lib/daemon.ex

defmodule NervesBackdoor.Daemon do
  use GenServer

  @spawn_opts [:stderr_to_stdout, :binary, :stream, {:line, 255}]

  # {:ok, pid} = NervesBackdoor.Daemon.start_link "/bin/daemon"
  # :ok = GenServer.stop pid
  def start_link(path) do
    GenServer.start_link(__MODULE__, path)
  end

  @impl true
  def init(path) do
    port = run(path)
    {:ok, {path, port}}
  end

  @impl true
  def terminate(reason, _state = {path, port}) do
    IO.inspect({path, :terminate, reason})
    true = Port.close(port)
  end

  @impl true
  def handle_info({_port, {:data, data}}, state = {path, _}) do
    IO.inspect({path, :data, data})
    {:noreply, state}
  end

  @impl true
  def handle_info({:DOWN, _ref, :port, _port, reason}, _state = {path, _}) do
    IO.inspect({path, :down, reason})
    port = run(path)
    {:noreply, {path, port}}
  end

  defp run(path) do
    env = {:env, env(path)}
    opts = [env | @spawn_opts]
    IO.inspect({path, :run, env})
    port = Port.open({:spawn_executable, path}, opts)
    _ref = Port.monitor(port)
    port
  end

  defp env(path) do
    case File.read(path <> ".env") do
      {:ok, data} ->
        data
        |> String.split("\n")
        |> Enum.filter(&filter/1)
        |> Enum.map(&tuple/1)

      _ ->
        []
    end
  end

  defp filter(line) do
    String.contains?(line, "=")
  end

  defp tuple(line) do
    trimmed = String.trim(line)
    [n, v] = String.split(trimmed, "=", parts: 2)
    {String.to_charlist(n), String.to_charlist(v)}
  end
end