diff --git a/lib/nerves_hub_link/configurator.ex b/lib/nerves_hub_link/configurator.ex index c2faf85..c4bd57a 100644 --- a/lib/nerves_hub_link/configurator.ex +++ b/lib/nerves_hub_link/configurator.ex @@ -3,7 +3,7 @@ defmodule NervesHubLink.Configurator do alias __MODULE__.Config require Logger - @device_api_version "2.0.0" + @device_api_version "2.1.0" @console_version "2.0.0" defmodule Config do diff --git a/lib/nerves_hub_link/script.ex b/lib/nerves_hub_link/script.ex new file mode 100644 index 0000000..077eaee --- /dev/null +++ b/lib/nerves_hub_link/script.ex @@ -0,0 +1,46 @@ +defmodule NervesHubLink.Script do + use GenServer + + @doc """ + Run a script from NervesHub and capture its output + """ + def capture(text, ref) do + _ = GenServer.start_link(__MODULE__, {self(), text, ref}) + + :ok + end + + @impl GenServer + def init({pid, text, ref}) do + state = %{pid: pid, text: text, ref: ref} + {:ok, state, {:continue, :capture}} + end + + @impl GenServer + def handle_continue(:capture, state) do + # Inspired from ExUnit.CaptureIO + # https://github.com/elixir-lang/elixir/blob/main/lib/ex_unit/lib/ex_unit/capture_io.ex + {:ok, string_io} = StringIO.open("") + + Process.group_leader(self(), string_io) + + {result, output} = + try do + Code.eval_string(state.text) + catch + kind, reason -> + {:ok, {_input, output}} = StringIO.close(string_io) + result = Exception.format_banner(kind, reason, __STACKTRACE__) + output = output <> "\n" <> result + {nil, output} + else + {result, _binding} -> + {:ok, {_input, output}} = StringIO.close(string_io) + {result, output} + end + + send(state.pid, {"scripts/run", state.ref, output, result}) + + {:stop, :normal, state} + end +end diff --git a/lib/nerves_hub_link/socket.ex b/lib/nerves_hub_link/socket.ex index eb4c7d4..794ad8e 100644 --- a/lib/nerves_hub_link/socket.ex +++ b/lib/nerves_hub_link/socket.ex @@ -9,6 +9,7 @@ defmodule NervesHubLink.Socket do alias NervesHubLink.Client alias NervesHubLink.Configurator alias NervesHubLink.Configurator.SharedSecret + alias NervesHubLink.Script alias NervesHubLink.UpdateManager alias NervesHubLink.UploadFile @@ -306,6 +307,12 @@ defmodule NervesHubLink.Socket do {:ok, socket} end + def handle_message(@device_topic, "scripts/run", params, socket) do + # See related handle_info for pushing back the script result + :ok = Script.capture(params["text"], params["ref"]) + {:ok, socket} + end + def handle_message(@device_topic, "archive", params, socket) do {:ok, info} = NervesHubLink.Message.ArchiveInfo.parse(params) _ = ArchiveManager.apply_archive(info, socket.assigns.config.archive_public_keys) @@ -410,6 +417,17 @@ defmodule NervesHubLink.Socket do end end + def handle_info({"scripts/run", ref, output, return}, socket) do + _ = + push(socket, @device_topic, "scripts/run", %{ + ref: ref, + output: output, + return: inspect(return, pretty: true) + }) + + {:noreply, socket} + end + def handle_info({:tty_data, data}, socket) do _ = push(socket, @console_topic, "up", %{data: data}) {:noreply, set_iex_timer(socket)}