From 391eaa1c5b11e875d13cc5efaf8ad04a469e4780 Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 16:51:40 -0400 Subject: [PATCH 1/7] Refactor configurator * Prevent PERIDIO_CONFIG_FILE from stopping the VM to present a better error. * Allow sane default values to be optional * Allow the application to be configured via config.exs with overrides from PERIDIO_CONFIG_FILE * Add remote_iex to enable the IEx remote console shell. If this is enabled, the shell will default to IEx * remote_shell will continue to open a getty * Allow setting fwup extra_args. * Allow setting fwup env --- lib/peridiod/application.ex | 3 +- lib/peridiod/configurator.ex | 175 +++++++++++++++++------------------ 2 files changed, 85 insertions(+), 93 deletions(-) diff --git a/lib/peridiod/application.ex b/lib/peridiod/application.ex index 8373e91..f55f7f8 100644 --- a/lib/peridiod/application.ex +++ b/lib/peridiod/application.ex @@ -11,10 +11,11 @@ defmodule Peridiod.Application do def start(_type, _args) do application_config = Application.get_all_env(:peridiod) + configurator_config = struct(Configurator.Config, application_config) children = [ {KV, application_config}, - Configurator, + {Configurator, configurator_config}, UpdateManager, Connection, Socket diff --git a/lib/peridiod/configurator.ex b/lib/peridiod/configurator.ex index e6c4294..bf166e0 100644 --- a/lib/peridiod/configurator.ex +++ b/lib/peridiod/configurator.ex @@ -14,10 +14,16 @@ defmodule Peridiod.Configurator do defstruct device_api_host: "device.cremini.peridio.com", device_api_port: 443, device_api_sni: "device.cremini.peridio.com", + device_api_verify: :verify_peer, + device_api_ca_certificate_path: nil, + key_pair_source: "env", + key_pair_config: %{"private_key" => nil, "certificate" => nil}, fwup_public_keys: [], fwup_devpath: "/dev/mmcblk0", fwup_env: [], + fwup_extra_args: [], params: %{}, + remote_shell: false, remote_iex: false, socket: [], ssl: [] @@ -26,11 +32,15 @@ defmodule Peridiod.Configurator do device_api_host: String.t(), device_api_port: String.t(), device_api_sni: charlist(), + device_api_verify: :verify_peer | :verify_none, + device_api_ca_certificate_path: Path.t(), fwup_public_keys: [binary()], fwup_devpath: Path.t(), fwup_env: [{String.t(), String.t()}], + fwup_extra_args: [String.t()], params: map(), remote_iex: boolean, + remote_shell: boolean, socket: any(), ssl: [:ssl.tls_client_option()] } @@ -38,8 +48,8 @@ defmodule Peridiod.Configurator do @callback build(%Config{}) :: Config.t() - def start_link(_opts) do - GenServer.start_link(__MODULE__, nil, name: __MODULE__) + def start_link(%Config{} = config) do + GenServer.start_link(__MODULE__, config, name: __MODULE__) end def get_config() do @@ -68,129 +78,112 @@ defmodule Peridiod.Configurator do |> Path.join("peridio/peridio-config.json") end - def init(nil) do - {:ok, build()} + def init(%Config{} = config) do + {:ok, build(config)} end def handle_call(:get_config, _from, config) do {:reply, config, config} end - @spec build :: Config.t() - defp build() do - resolve_config() - |> build_config(base_config()) + @spec build(Config.t()) :: Config.t() + defp build(config) do + config + |> base_config() + |> build_config(resolve_config()) |> add_socket_opts() end - defp atomize_config(config) do - %{ - "device_api" => %{ - "certificate_path" => cremini_device_api_certificate_path, - "url" => cremini_device_api_url, - "verify" => cremini_device_api_verify - }, - "fwup" => %{ - "devpath" => cremini_fwup_devpath, - "public_keys" => cremini_fwup_public_keys - }, - "remote_shell" => cremini_remote_shell, - "node" => %{ - "key_pair_source" => key_pair_source, - "key_pair_config" => key_pair_config - }, - "version" => 1 - } = config - - %{ - device_api: %{ - certificate_path: cremini_device_api_certificate_path, - url: cremini_device_api_url, - verify: cremini_device_api_verify - }, - fwup: %{ - devpath: cremini_fwup_devpath, - public_keys: cremini_fwup_public_keys - }, - remote_shell: cremini_remote_shell, - node: %{ - key_pair_source: key_pair_source, - key_pair_config: key_pair_config - } - } - end - defp resolve_config do - path = - System.fetch_env("PERIDIO_CONFIG_FILE") - |> case do - {:ok, path} -> path - :error -> default_path() - end - + path = config_path() debug("using config path: #{path}") - path - |> File.read() - |> case do - {:ok, config_json} -> - config_json - + with {:ok, file} <- File.read(path), + {:ok, config} <- Jason.decode(file) do + config + else {:error, e} -> error(%{message: "unable to read peridio config file", file_read_error: e}) - System.stop(1) + %{} end - |> Jason.decode() - |> case do - {:ok, config} -> - atomize_config(config) + end - {:error, e} -> - error(%{message: "unable to decode peridio config file", json_decode_error: e}) - raise {:error, :decode_json} - end + defp config_path() do + System.get_env("PERIDIO_CONFIG_FILE", default_path()) end - defp build_config(peridio_config, base_config) do + defp build_config(%Config{} = config, config_file) do + {host, port} = + case config_file["device_api"]["url"] do + nil -> + {nil, nil} + + url -> + parts = String.split(url, ":") + {Enum.at(parts, 0), Enum.at(parts, 1)} + end + + config = + config + |> Map.put( + :device_api_ca_certificate_path, + Application.app_dir(:peridiod, "priv/peridio-cert.pem") + ) + |> override_if_set( + :device_api_ca_certificate_path, + config_file["device_api"]["certificate_path"] + ) + |> override_if_set(:device_api_host, host) + |> override_if_set(:device_api_port, port) + |> override_if_set(:device_api_verify, config_file["device_api"]["verify"]) + |> override_if_set(:fwup_devpath, config_file["fwup"]["devpath"]) + |> override_if_set(:fwup_public_keys, config_file["fwup"]["public_keys"]) + |> override_if_set(:fwup_env, config_file["fwup"]["env"]) + |> override_if_set(:fwup_extra_args, config_file["fwup"]["extra_args"]) + |> override_if_set(:remote_shell, config_file["remote_shell"]) + |> override_if_set(:remote_iex, config_file["remote_iex"]) + |> override_if_set(:key_pair_source, config_file["node"]["key_pair_source"]) + |> override_if_set(:key_pair_config, config_file["node"]["key_pair_config"]) + verify = - case peridio_config.device_api.verify do + case config.device_api_verify do true -> :verify_peer false -> :verify_none + value when is_atom(value) -> value end - config = %{ - base_config - | device_api_host: peridio_config.device_api.url, - device_api_sni: peridio_config.device_api.url, - fwup_devpath: peridio_config.fwup.devpath, - fwup_public_keys: peridio_config.fwup.public_keys, - socket: [url: "wss://#{peridio_config.device_api.url}:443/socket/websocket"], - remote_iex: peridio_config.remote_shell, - ssl: [ - server_name_indication: to_charlist(peridio_config.device_api.url), - verify: verify, - cacertfile: peridio_config.device_api.certificate_path - ] - } - - case peridio_config.node.key_pair_source do + config = + config + |> Map.put(:socket, + url: "wss://#{config.device_api_host}:#{config.device_api_port}/socket/websocket" + ) + |> Map.put(:ssl, + server_name_indication: to_charlist(config.device_api_host), + verify: verify, + cacertfile: config.device_api_ca_certificate_path + ) + + case config.key_pair_source do "file" -> - Configurator.File.config(peridio_config.node.key_pair_config, config) + Configurator.File.config(config.key_pair_config, config) "pkcs11" -> - Configurator.PKCS11.config(peridio_config.node.key_pair_config, config) + Configurator.PKCS11.config(config.key_pair_config, config) "uboot-env" -> - Configurator.UBootEnv.config(peridio_config.node.key_pair_config, config) + Configurator.UBootEnv.config(config.key_pair_config, config) "env" -> - Configurator.Env.config(peridio_config.node.key_pair_config, config) + Configurator.Env.config(config.key_pair_config, config) type -> Logger.error("Unknown key pair type: #{type}") end end + defp override_if_set(%Config{} = config, _key, value) when is_nil(value), do: config + defp override_if_set(%Config{} = config, key, value), do: Map.replace(config, key, value) + defp add_socket_opts(config) do # PhoenixClient requires these SSL options be passed as # [transport_opts: [socket_opts: ssl]]. So for convenience, @@ -213,9 +206,7 @@ defmodule Peridiod.Configurator do %{config | socket: socket} end - defp base_config() do - base = struct(Config, Application.get_all_env(:peridiod)) - + defp base_config(base) do url = "wss://#{base.device_api_host}:#{base.device_api_port}/socket/websocket" socket = Keyword.put_new(base.socket, :url, url) From 844a8bd86cf8bb80495b88bb1f6b7017f3faaa74 Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:12:16 -0400 Subject: [PATCH 2/7] Allow passing extra_args and env to fwup --- lib/peridiod/fwup_config.ex | 6 ++++++ lib/peridiod/update_manager.ex | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/peridiod/fwup_config.ex b/lib/peridiod/fwup_config.ex index 0819897..3aff5e4 100644 --- a/lib/peridiod/fwup_config.ex +++ b/lib/peridiod/fwup_config.ex @@ -8,6 +8,7 @@ defmodule Peridiod.FwupConfig do defstruct fwup_public_keys: [], fwup_devpath: "/dev/mmcblk0", fwup_env: [], + fwup_extra_args: [], fwup_task: "upgrade", handle_fwup_message: nil, update_available: nil @@ -37,10 +38,15 @@ defmodule Peridiod.FwupConfig do fwup_devpath: Path.t(), fwup_task: String.t(), fwup_env: [{String.t(), String.t()}], + fwup_extra_args: [String.t()], handle_fwup_message: handle_fwup_message_fun, update_available: update_available_fun } + + def parse_fwup_env(%{} = fwup_env), do: Enum.to_list(fwup_env) + def parse_fwup_env([_] = fwup_env), do: fwup_env + @doc "Raises an ArgumentError on invalid arguments" @spec validate!(t()) :: t() def validate!(%__MODULE__{} = args) do diff --git a/lib/peridiod/update_manager.ex b/lib/peridiod/update_manager.ex index 87cdf89..d1e3d62 100644 --- a/lib/peridiod/update_manager.ex +++ b/lib/peridiod/update_manager.ex @@ -107,7 +107,8 @@ defmodule Peridiod.UpdateManager do fwup_config = %FwupConfig{ fwup_public_keys: config.fwup_public_keys, fwup_devpath: config.fwup_devpath, - fwup_env: config.fwup_env, + fwup_env: FwupConfig.parse_fwup_env(config.fwup_env), + fwup_extra_args: config.fwup_extra_args, handle_fwup_message: &Client.handle_fwup_message/1, update_available: &Client.update_available/1 } @@ -260,7 +261,14 @@ defmodule Peridiod.UpdateManager do @spec fwup_args(FwupConfig.t()) :: [String.t()] defp fwup_args(%FwupConfig{} = config) do - args = ["--apply", "--no-unmount", "-d", config.fwup_devpath, "--task", config.fwup_task] + args = [ + "--apply", + "--no-unmount", + "-d", + config.fwup_devpath, + "--task", + config.fwup_task + ] ++ config.fwup_extra_args Enum.reduce(config.fwup_public_keys, args, fn public_key, args -> args ++ ["--public-key", public_key] From 4267bba2f29a63d4400b88769b080225560ebe51 Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:12:49 -0400 Subject: [PATCH 3/7] Add openssl3 pkcs11 engine search paths --- lib/peridiod/configurator/pkcs11.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/peridiod/configurator/pkcs11.ex b/lib/peridiod/configurator/pkcs11.ex index 0aa05de..6629da9 100644 --- a/lib/peridiod/configurator/pkcs11.ex +++ b/lib/peridiod/configurator/pkcs11.ex @@ -33,7 +33,9 @@ defmodule Peridiod.Configurator.PKCS11 do defp pkcs11_path() do [ "/usr/lib/engines-1.1/libpkcs11.so", - "/usr/lib/engines/libpkcs11.so" + "/usr/lib/engines/libpkcs11.so", + "/usr/lib/engines-3/libpkcs11.so", + "/usr/lib64/engines-3/libpkcs11.so" ] |> Enum.find(&File.exists?/1) end From f5de06cb9e9b8311949e68f7b0bdbeba822cc9e9 Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:13:21 -0400 Subject: [PATCH 4/7] Refactor remote_console to support iex and getty --- lib/peridiod/{ => remote_console}/getty.ex | 10 +- lib/peridiod/remote_console/iex.ex | 70 ++++++++++++++ lib/peridiod/socket.ex | 104 ++++++++++++++------- mix.exs | 1 + mix.lock | 23 ++--- 5 files changed, 159 insertions(+), 49 deletions(-) rename lib/peridiod/{ => remote_console}/getty.ex (93%) create mode 100644 lib/peridiod/remote_console/iex.ex diff --git a/lib/peridiod/getty.ex b/lib/peridiod/remote_console/getty.ex similarity index 93% rename from lib/peridiod/getty.ex rename to lib/peridiod/remote_console/getty.ex index 5071df5..c692c04 100644 --- a/lib/peridiod/getty.ex +++ b/lib/peridiod/remote_console/getty.ex @@ -1,4 +1,4 @@ -defmodule Peridiod.Getty do +defmodule Peridiod.RemoteConsole.Getty do use GenServer require Logger @@ -20,6 +20,10 @@ defmodule Peridiod.Getty do GenServer.call(pid, {:send_data, data}) end + def window_change(_width, _height) do + :ok + end + @impl true def init(opts) do send(self(), :start) @@ -77,7 +81,7 @@ defmodule Peridiod.Getty do end def handle_info({:circuits_uart, _pid, data}, state) do - send(state.callback, {:getty, self(), data}) + send(state.callback, {:remote_console, self(), data}) {:noreply, state, state.timeout} end @@ -94,7 +98,7 @@ defmodule Peridiod.Getty do GenServer.stop(state.getty_pid) GenServer.stop(state.uart_pid) GenServer.stop(state.pty_pid) - send(state.callback, {:getty, self(), :timeout}) + send(state.callback, {:remote_console, self(), :timeout}) {:stop, :normal, state} end diff --git a/lib/peridiod/remote_console/iex.ex b/lib/peridiod/remote_console/iex.ex new file mode 100644 index 0000000..3e57ea0 --- /dev/null +++ b/lib/peridiod/remote_console/iex.ex @@ -0,0 +1,70 @@ +defmodule Peridiod.RemoteConsole.IEx do + use GenServer + + # 5 minutes + @timeout 300_000 + + def start_link(opts) do + opts = Keyword.put_new(opts, :callback, self()) + GenServer.start_link(__MODULE__, opts) + end + + def stop(pid) do + GenServer.stop(pid) + end + + def send_data(pid, data) do + GenServer.cast(pid, {:send_data, data}) + end + + def window_change(pid, width, height) do + GenServer.cast(pid, {:window_change, width, height}) + end + + def init(opts) do + timeout = opts[:timeout] || @timeout + shell_opts = [[dot_iex_path: dot_iex_path()]] + {:ok, iex_pid} = ExTTY.start_link(handler: self(), type: :elixir, shell_opts: shell_opts) + + {:ok, + %{ + iex_pid: iex_pid, + iex_timer: Process.send_after(self(), :iex_timeout, timeout), + queue: [], + callback: opts[:callback], + timeout: timeout + }} + end + + def handle_cast({:send_data, data}, %{iex_pid: iex_pid} = state) do + _ = ExTTY.send_text(iex_pid, data) + {:noreply, set_iex_timer(state)} + end + + def handle_cast({:window_change, width, height}, %{iex_pid: iex_pid} = state) do + _ = ExTTY.window_change(iex_pid, width, height) + {:noreply, set_iex_timer(state)} + end + + def handle_info(:iex_timeout, state) do + GenServer.stop(state.iex_pid) + send(state.callback, {:remote_console, self(), :timeout}) + {:stop, :normal, state} + end + + def handle_info({:tty_data, data}, state) do + send(state.callback, {:remote_console, self(), data}) + {:noreply, set_iex_timer(state)} + end + + defp set_iex_timer(%{iex_timer: old_timer} = state) do + _ = if old_timer, do: Process.cancel_timer(old_timer) + %{state | iex_timer: Process.send_after(self(), :iex_timeout, state.timeout)} + end + + defp dot_iex_path() do + [".iex.exs", "~/.iex.exs", "/etc/iex.exs"] + |> Enum.map(&Path.expand/1) + |> Enum.find("", &File.regular?/1) + end +end diff --git a/lib/peridiod/socket.ex b/lib/peridiod/socket.ex index bdf4f70..3c37fed 100644 --- a/lib/peridiod/socket.ex +++ b/lib/peridiod/socket.ex @@ -5,6 +5,7 @@ defmodule Peridiod.Socket do alias Peridiod.Client alias Peridiod.UpdateManager alias Peridiod.Configurator + alias Peridiod.RemoteConsole @rejoin_after Application.compile_env(:peridiod, :rejoin_after, 5_000) @@ -62,7 +63,9 @@ defmodule Peridiod.Socket do new_socket() |> assign(params: config.params) |> assign(remote_iex: config.remote_iex) - |> assign(getty_pid: nil) + |> assign(remote_shell: config.remote_shell) + |> assign(remote_console_pid: nil) + |> assign(remote_console_timer: nil) |> connect!(opts) Process.flag(:trap_exit, true) @@ -95,7 +98,8 @@ defmodule Peridiod.Socket do end def handle_join(@console_topic, _reply, socket) do - Logger.debug("[#{inspect(__MODULE__)}] Joined Console channel") + protocol = if socket.assigns.remote_iex, do: "IEx", else: "getty" + Logger.debug("[#{inspect(__MODULE__)}] Joined Console channel: #{protocol}") {:ok, socket} end @@ -144,9 +148,12 @@ defmodule Peridiod.Socket do def handle_message( @console_topic, "window_size", - %{"height" => _height, "width" => _width}, + %{"height" => height, "width" => width}, socket ) do + if socket.assigns.remote_iex do + RemoteConsole.IEx.window_change(socket.assigns.remote_console_pid, width, height) + end {:ok, socket} end @@ -166,36 +173,50 @@ defmodule Peridiod.Socket do # Console API messages # def handle_message(@console_topic, "restart", _payload, socket) do - Logger.warning("[#{inspect(__MODULE__)}] Restarting IEx process from web request") - - _ = push(socket, @console_topic, "up", %{data: "\r*** Restarting IEx ***\r"}) + protocol = if socket.assigns.remote_iex, do: "IEx", else: "getty" + Logger.warning("[#{inspect(__MODULE__)}] Restarting #{protocol} process from web request") + _ = push(socket, @console_topic, "up", %{data: "\r*** Restarting #{protocol} ***\r"}) socket = socket - |> stop_getty() - |> start_getty() + |> stop_remote_console() + |> start_remote_console() {:ok, socket} end - def handle_message(@console_topic, message, payload, %{assigns: %{getty_pid: nil}} = socket) do - stop_getty(socket) - handle_message(@console_topic, message, payload, start_getty(socket)) + def handle_message( + @console_topic, + message, + payload, + %{assigns: %{remote_console_pid: nil}} = socket + ) do + stop_remote_console(socket) + handle_message(@console_topic, message, payload, start_remote_console(socket)) end - def handle_message(@console_topic, "dn", %{"data" => data}, socket) do - Peridiod.Getty.send_data(socket.assigns.getty_pid, data) + def handle_message( + @console_topic, + "dn", + %{"data" => data}, + %{assigns: %{remote_shell: true}} = socket + ) do + RemoteConsole.Getty.send_data(socket.assigns.remote_console_pid, data) {:ok, socket} end - @impl Slipstream - def handle_info({:tty_data, data}, socket) do - data = remove_unwanted_chars(data) - _ = push(socket, @console_topic, "up", %{data: data}) - {:noreply, socket} + def handle_message( + @console_topic, + "dn", + %{"data" => data}, + %{assigns: %{remote_iex: true}} = socket + ) do + RemoteConsole.IEx.send_data(socket.assigns.remote_console_pid, data) + {:ok, socket} end - def handle_info({:getty, _, :timeout}, socket) do + @impl Slipstream + def handle_info({:remote_console, _, :timeout}, socket) do msg = """ \r ****************************************\r @@ -207,23 +228,23 @@ defmodule Peridiod.Socket do _ = push(socket, @console_topic, "up", %{data: msg}) - {:noreply, stop_getty(socket)} + {:noreply, stop_remote_console(socket)} end - def handle_info({:getty, _, {:error, :eio}}, socket) do - msg = "\r******* Remote Console stopped: eio *******\r" + def handle_info({:remote_console, _, {:error, error}}, socket) do + msg = "\r******* Remote Console stopped: #{inspect(error)} *******\r" _ = push(socket, @console_topic, "up", %{data: msg}) Logger.warning(msg) socket = socket - |> stop_getty() - |> start_getty() + |> stop_remote_console() + |> start_remote_console() {:noreply, socket} end - def handle_info({:getty, _pid, data}, socket) do + def handle_info({:remote_console, _pid, data}, socket) do data = remove_unwanted_chars(data) _ = push(socket, @console_topic, "up", %{data: data}) {:noreply, socket} @@ -274,28 +295,41 @@ defmodule Peridiod.Socket do defp handle_join_reply(_), do: :noop defp maybe_join_console(socket) do - if socket.assigns.remote_iex do + if socket.assigns.remote_iex || socket.assigns.remote_shell do join(socket, @console_topic, socket.assigns.params) else socket end end - defp start_getty(socket) do - Logger.debug("Start getty") - {:ok, getty_pid} = Peridiod.Getty.start_link([]) + defp start_remote_console(%{assigns: %{remote_iex: true}} = socket) do + Logger.info("Remote Console: Start IEx") + {:ok, iex_pid} = RemoteConsole.IEx.start_link([]) + assign(socket, remote_console_pid: iex_pid) + end - socket - |> assign(getty_pid: getty_pid) + defp start_remote_console(%{assigns: %{remote_shell: true}} = socket) do + Logger.debug("Remote Console: Start getty") + {:ok, getty_pid} = RemoteConsole.Getty.start_link([]) + assign(socket, remote_console_pid: getty_pid) end - defp stop_getty(%{assigns: %{getty_pid: nil}} = socket), do: socket + defp stop_remote_console(%{assigns: %{remote_console_pid: nil}} = socket), do: socket + + defp stop_remote_console(%{assigns: %{remote_iex: true, remote_console_pid: iex_pid}} = socket) do + _ = if Process.alive?(iex_pid), do: RemoteConsole.IEx.stop(iex_pid) + + socket + |> assign(remote_console_pid: nil) + end - defp stop_getty(%{assigns: %{getty_pid: getty_pid}} = socket) do - _ = if Process.alive?(getty_pid), do: Peridiod.Getty.stop(getty_pid) + defp stop_remote_console( + %{assigns: %{remote_shell: true, remote_console_pid: getty_pid}} = socket + ) do + _ = if Process.alive?(getty_pid), do: RemoteConsole.Getty.stop(getty_pid) socket - |> assign(getty_pid: nil) + |> assign(remote_console_pid: nil) end defp remove_unwanted_chars(input) when is_binary(input) do diff --git a/mix.exs b/mix.exs index 1d98361..1368216 100644 --- a/mix.exs +++ b/mix.exs @@ -23,6 +23,7 @@ defmodule Peridiod.MixProject do defp deps do [ + {:extty, "~> 0.2"}, {:muontrap, "~> 1.3"}, {:circuits_uart, "~> 1.5"}, {:castore, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 9352661..1eb1051 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,25 @@ %{ - "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "circuits_uart": {:hex, :circuits_uart, "1.5.1", "c28f0144db799a8dbe84e1a05a58808450dd13a48b4e8ed92aa0a785ccfbbb1d", [:mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "e0690f814e39c0e7cc742ccd1421d5489f6a7627477ff64c1dbaf7520c1b5600"}, - "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, + "circuits_uart": {:hex, :circuits_uart, "1.5.2", "2b9bed7bfd4051ad199e247e6564e04ab0b363b04277c9b1140df533c39ffa59", [:mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0ae3184cd25efeee8d36cde241581d0db15f67b9bc3a1596cba8eec6c8f6ab99"}, + "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, + "extty": {:hex, :extty, "0.3.0", "55bd8881d00e70f92f9f60a9a3d6907d5cb6964c7992a4c812cc4f0a119696ec", [:mix], [], "hexpm", "d8ad88e38159b30aa75d33845f6342e429f832cd5ed043768d6fb57374d0c862"}, "fwup": {:hex, :fwup, "1.1.0", "6c9d5f06a38263855dcf34f5a5b4da46c2ca42f6e3f873b8f932d095ce010765", [:mix], [], "hexpm", "3ff61d932a42785efc3324ecab6cf9bd3de75dacfca16d9e659a7173ef9f15d6"}, - "hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, "mint_web_socket": {:hex, :mint_web_socket, "1.0.3", "aab42fff792a74649916236d0b01f560a0b3f03ca5dea693c230d1c44736b50e", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "ca3810ca44cc8532e3dce499cc17f958596695d226bb578b2fbb88c09b5954b0"}, - "muontrap": {:hex, :muontrap, "1.3.2", "8942689f0727160d97d61ff25156fb35b7a10e4e925c88378a69a4041cc04257", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "e29715dafdd8184c68516bad103ba1d58c7b3a7e1f5311fbf75c1d617caefa56"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "muontrap": {:hex, :muontrap, "1.5.0", "bf5c273872379968615a39974458328209ac97fa1f588396192131ff973d1ca2", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "daf605e877f60b5be9215e3420d7971fc468677b29921e40915b15fd928273d4"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "slipstream": {:hex, :slipstream, "1.1.0", "e3581e9bc73036e4283b33447475499d18c813c7662aa6b86e131633a7e912f3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint_web_socket, "~> 0.2 or ~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.1 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "66eb1ac7c43573511b5bad90c24c128bb4e69f588bff65d0c409adf4c7eb02e6"}, + "slipstream": {:hex, :slipstream, "1.1.1", "7e56f62f1a9ee81351e3c36f57b9b187e00dc2f470e70ba46ea7ad16e80b061f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint_web_socket, "~> 0.2 or ~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.1 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c20e420cde1654329d38ec3aa1c0e4debbd4c91ca421491e7984ad4644e638a6"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "uboot_env": {:hex, :uboot_env, "1.0.1", "b0e136cf1a561412ff7db23ed2b6df18d7c7ce2fc59941afd851006788a67f3d", [:mix], [], "hexpm", "b6d4fe7c24123be57ed946c48116d23173e37944bc945b8b76fccc437909c60b"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "x509": {:hex, :x509, "0.8.7", "576130ad83731613fdb5787917f2fadbe308b6f5a48ed6e865a4b6be3fa802d0", [:mix], [], "hexpm", "3604125d6a0171da6e8a935810b58c999fccab0e3d20b2ed28d97fa2d9e2f6b4"}, + "x509": {:hex, :x509, "0.8.8", "aaf5e58b19a36a8e2c5c5cff0ad30f64eef5d9225f0fd98fb07912ee23f7aba3", [:mix], [], "hexpm", "ccc3bff61406e5bb6a63f06d549f3dba3a1bbb456d84517efaaa210d8a33750f"}, } From 8509dd864d09b42b7afd9ad47a4225a0ed1089ee Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:13:58 -0400 Subject: [PATCH 5/7] Update docker support to fix broken builds when depending on musl-c --- .dockerignore | 2 + Dockerfile | 68 ++++++++++++++++++++++++++------ support/.gitignore | 3 ++ support/Dockerfile-release.amd64 | 11 ++++-- support/Dockerfile-release.arm64 | 11 ++++-- support/fwup.conf | 30 ++++++++++++++ support/peridio.json | 33 ++++++++-------- 7 files changed, 120 insertions(+), 38 deletions(-) create mode 100644 .dockerignore create mode 100644 support/.gitignore create mode 100644 support/fwup.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0304f65 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +_build +deps diff --git a/Dockerfile b/Dockerfile index 5360d30..d00b845 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,83 @@ -FROM elixir:1.16.2-alpine AS build +FROM hexpm/elixir:1.16.2-erlang-26.2.5-ubuntu-noble-20240429 AS build ARG MIX_ENV=prod +ARG UBOOT_ENV_SIZE=0x20000 +ENV DEBIAN_FRONTEND=noninteractive -RUN apk add --no-cache --update-cache \ - git \ - make +RUN apt-get update \ + && apt-get install -y \ + git make build-essential \ + u-boot-tools \ + libsodium-dev \ + autotools-dev \ + autoconf \ + libtool \ + pkg-config \ + libarchive-dev \ + libconfuse-dev \ + zlib1g-dev \ + xdelta3 \ + dosfstools \ + help2man \ + curl \ + mtools \ + unzip \ + zip RUN mix archive.install github hexpm/hex branch latest --force RUN /usr/local/bin/mix local.rebar --force +# Build fwup here to ensure that its built for the arch +WORKDIR /opt +RUN git clone https://github.com/fwup-home/fwup --depth 1 --branch v1.10.2 +WORKDIR /opt/fwup +RUN ./autogen.sh && PKG_CONFIG_PATH=$PKG_CONFIG_PATH ./configure --enable-shared=no && make && make install + RUN mkdir -p /opt/app ADD . /opt/app/ WORKDIR /opt/app +RUN echo "/etc/peridiod/uboot.env 0x0000 ${UBOOT_ENV_SIZE}" > /opt/app/support/fw_env.config +RUN mkenvimage -s ${UBOOT_ENV_SIZE} -o /opt/app/support/uboot.env /opt/app/support/uEnv.txt +RUN PERIDIO_META_PRODUCT=peridiod \ + PERIDIO_META_DESCRIPTION=peridiod-dev \ + PERIDIO_META_VERSION=1.0.0 \ + PERIDIO_META_PLATFORM=docker \ + PERIDIO_META_ARCHITECTURE=docker \ + PERIDIO_META_AUTHOR=peridio \ + fwup -c -f support/fwup.conf -o support/peridiod.fw +RUN fwup -a -t complete -i support/peridiod.fw -d support/peridiod.img RUN mix deps.get --only $MIX_ENV RUN mix release --overwrite -FROM alpine:3.19 as app +FROM ubuntu:noble as app + +ENV DEBIAN_FRONTEND=noninteractive -RUN apk --no-cache add wireguard-tools iptables ip6tables inotify-tools libstdc++ libgcc u-boot-tools socat iproute2-ss agetty cgroup-tools -RUN apk --no-cache add 'fwup~=1.10' \ - --repository http://nl.alpinelinux.org/alpine/edge/community/ +RUN apt-get update && apt-get -y install locales socat libconfuse-dev libarchive-dev && apt-get clean +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \ + locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +RUN useradd -ms /bin/bash peridio +RUN echo "peridio:peridio" | chpasswd ENV PERIDIO_CONFIG_FILE=/etc/peridiod/peridio.json -ARG UBOOT_ENV_SIZE=0x20000 RUN mkdir -p /etc/peridiod RUN mkdir -p /boot +RUN echo "echo \"Reboot Requested\"" > /usr/bin/reboot && chmod +x /usr/bin/reboot COPY --from=build /opt/app/priv/peridio-cert.pem /etc/peridiod/peridio-cert.pem COPY --from=build /opt/app/_build/prod/rel/peridiod /opt/peridiod COPY --from=build /opt/app/support/peridio.json /etc/peridiod/peridio.json -COPY --from=build /opt/app/support/uEnv.txt /etc/peridiod/uEnv.txt - -RUN echo "/etc/peridiod/uboot.env 0x0000 ${UBOOT_ENV_SIZE}" > /etc/fw_env.config -RUN mkenvimage -s ${UBOOT_ENV_SIZE} -o /etc/peridiod/uboot.env /etc/peridiod/uEnv.txt +COPY --from=build /opt/app/support/uboot.env /etc/peridiod/uboot.env +COPY --from=build /opt/app/support/fw_env.config /etc/fw_env.config +COPY --from=build /opt/app/support/peridiod.img /etc/peridiod/peridiod.img +COPY --from=build /opt/fwup/src/fwup /usr/bin/fwup WORKDIR /opt/peridiod CMD ["/opt/peridiod/bin/peridiod", "start_iex"] diff --git a/support/.gitignore b/support/.gitignore new file mode 100644 index 0000000..20ae39a --- /dev/null +++ b/support/.gitignore @@ -0,0 +1,3 @@ +peridiod.fw +peridiod.img +peridiod-signed.fw diff --git a/support/Dockerfile-release.amd64 b/support/Dockerfile-release.amd64 index b7c2a8c..7431d41 100644 --- a/support/Dockerfile-release.amd64 +++ b/support/Dockerfile-release.amd64 @@ -1,14 +1,17 @@ -FROM elixir:1.16.2-alpine AS build +FROM --platform=linux/amd64 hexpm/elixir:1.16.2-erlang-26.2.5-ubuntu-noble-20240429 AS build ARG MIX_ENV=prod +ARG UBOOT_ENV_SIZE=0x20000 +ENV DEBIAN_FRONTEND=noninteractive -RUN apk add --no-cache --update-cache \ - git \ - make +RUN apt-get update \ + && apt-get install -y \ + git make build-essential RUN mix archive.install github hexpm/hex branch latest --force RUN /usr/local/bin/mix local.rebar --force + RUN mkdir -p /opt/app ADD . /opt/app/ diff --git a/support/Dockerfile-release.arm64 b/support/Dockerfile-release.arm64 index cd02ed1..99a086f 100644 --- a/support/Dockerfile-release.arm64 +++ b/support/Dockerfile-release.arm64 @@ -1,14 +1,17 @@ -FROM --platform=linux/arm64/v8 elixir:1.16.2-alpine AS build +FROM --platform=linux/arm64/v8 hexpm/elixir:1.16.2-erlang-26.2.5-ubuntu-noble-20240429 AS build ARG MIX_ENV=prod +ARG UBOOT_ENV_SIZE=0x20000 +ENV DEBIAN_FRONTEND=noninteractive -RUN apk add --no-cache --update-cache \ - git \ - make +RUN apt-get update \ + && apt-get install -y \ + git make build-essential RUN mix archive.install github hexpm/hex branch latest --force RUN /usr/local/bin/mix local.rebar --force + RUN mkdir -p /opt/app ADD . /opt/app/ diff --git a/support/fwup.conf b/support/fwup.conf new file mode 100644 index 0000000..a89651f --- /dev/null +++ b/support/fwup.conf @@ -0,0 +1,30 @@ +# Firmware archive metadata +meta-product = ${PERIDIO_META_PRODUCT} +meta-description = ${PERIDIO_META_DESCRIPTION} +meta-version = ${PERIDIO_META_VERSION} +meta-platform = ${PERIDIO_META_PLATFORM} +meta-architecture = ${PERIDIO_META_ARCHITECTURE} +meta-author = ${PERIDIO_META_AUTHOR} +meta-vcs-identifier = ${PERIDIO_META_VCS_IDENTIFIER} +meta-misc = ${PERIDIO_META_MISC} + +task complete { + on-init { + info("Task Complete") + } +} + +task upgrade.a { + on-init { + info("Task Upgrading A") + execute("\${PERIDIO_EXECUTE}") + } + +} + +task upgrade.b { + on-init { + info("Task Upgrading B") + execute("\${PERIDIO_EXECUTE}") + } +} diff --git a/support/peridio.json b/support/peridio.json index 5b7c741..ee81a2e 100644 --- a/support/peridio.json +++ b/support/peridio.json @@ -1,20 +1,19 @@ { - "version": 1, - "device_api": { - "certificate_path": "/etc/peridiod/peridio-cert.pem", - "url": "device.cremini.peridio.com", - "verify": true - }, - "fwup": { - "devpath": "/dev/null", - "public_keys": [] - }, - "remote_shell": true, - "node": { - "key_pair_source": "env", - "key_pair_config": { - "private_key": "PERIDIO_PRIVATE_KEY", - "certificate": "PERIDIO_CERTIFICATE" - } + "version": 1, + "fwup": { + "devpath": "/etc/peridiod/peridiod.img", + "extra_args": ["--unsafe"], + "env": { + "PERIDIO_EXECUTE": "touch /tmp/peridio-upgrade" + } + }, + "remote_iex": false, + "remote_shell": true, + "node": { + "key_pair_source": "env", + "key_pair_config": { + "private_key": "PERIDIO_PRIVATE_KEY", + "certificate": "PERIDIO_CERTIFICATE" } } +} From 966881b9af50b7cae4ea4abb8986168584f4637d Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:14:05 -0400 Subject: [PATCH 6/7] Update readme --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 67fbd31..ef460bd 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,22 @@ Peridiod is configured via a json formatted file on the filesystem. The location * `fwup`: Keys related to the use of fwup for the last mile. * `devpath`: The block storage device path to use for applying firmware updates. * `public_keys`: A list of authorized public keys used when verifying update archives. -* `remote_shell`: Enable or disable the remote console feature. + * `extra_args`: A list of extra arguments to pass to the fwup command used for applying fwup archives. Helpful when needing to use the --unsafe flag in fwup. + * `env`: A json object of `"ENV_VAR": "value"` pairs to decorate the environment which fwup is executed from. +* `remote_shell`: Enable or disable the remote getty feature. +* `remote_iex`: Enable or disable the remote IEx feature. Useful if you are deploying a Nerves distribution. * `node`: Node configuration settings * `key_pair_source`: Options are `file`, `uboot-env`, `pkcs11`. This determines the source of the identity key information. * `key_pair_config`: Different depending on the `key_pair_source` - + `key_pair_source: file`: * `private_key_path`: Path on the filesystem to a PEM encoded private key file. * `certificate_path`: Path on the filesystem to a PEM encoded x509 certificate file. - + `key_pair_source: uboot-env`: * `private_key`: The key in the uboot environment which contains a PEM encoded private key. * `certificate`: The key in the uboot environment which contains a PEM encoded x509 certificate. - + `key_pair_source: pkcs11`: * `key_id`: The `PKCS11` URI used to for private key operations. Examples: @@ -99,3 +102,60 @@ PKCS11 Identity using ATECC608B TrustAndGo "cert_id": "pkcs11:token=MCHP;object=device;type=cert" } ``` + +### Configuring with Elixir + +The peridiod application can be set using config.exs in a Nerves based application. The following is an example of the keys that can be set: + +```elixir +config :peridiod, + device_api_host: "device.cremini.peridio.com", + device_api_port: 443, + device_api_sni: "device.cremini.peridio.com", + device_api_verify: :verify_peer, + device_api_ca_certificate_path: nil, + key_pair_source: "env", + key_pair_config: %{"private_key" => "PERIDIO_PRIVATE_KEY", "certificate" => "PERIDIO_CERTIFICATE"}, + fwup_public_keys: [], + fwup_devpath: "/dev/mmcblk0", + fwup_env: [], + fwup_extra_args: [], + remote_shell: false, + remote_iex: true, +``` + +## Running with Docker + +You can debug using docker by generating an SSL certificta eand private key pair that is trusted by Peridio Cloud and pass it into the container. + +Building the container: + +```bash +docker build --tag peridio/peridiod . +``` + +Running the container: + +```bash +podman run -it --rm --env PERIDIO_CERTIFICATE="$(cat /path/to/end-entity-certificate.pem)" --env PERIDIO_PRIVATE_KEY="$(cat /path/to/end-entity-private-key.pem)" peridio/peridiod:latest +``` + +The container will be built using the `peridio.json` configuration file in the support directory. For testing you can modify this as you please. It is configured by default to allow testing for the remote shell and even firmware updates using deployments. You can create firmware to test for deployments using the following: + +```bash +PERIDIO_META_PRODUCT=peridiod \ +PERIDIO_META_DESCRIPTION=peridiod-dev \ +PERIDIO_META_VERSION=1.0.1 \ +PERIDIO_META_PLATFORM=docker \ +PERIDIO_META_ARCHITECTURE=docker \ +PERIDIO_META_AUTHOR=peridio \ +fwup -c -f support/fwup.conf -o support/peridiod.fw +``` + +Then sign the firmwaare using a key pair that is trusted by Peridio Cloud + +```bash +fwup --sign -i support/peridiod.fw -o support/peridiod-signed.fw --public-key "$(cat ./path/to/fwup-key.pub)" --private-key "$(cat /path/to/fwup-key.priv)" +``` + +You can then upload `support/peridiod-signed.fw` to Peridio Cloud and configure a deployment. The container will not actually apply anything persistent, but it will simulate downloading and applying an update. From c6a30a97db9f39b6154396c5609f9d47b50f2def Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Wed, 15 May 2024 17:14:26 -0400 Subject: [PATCH 7/7] Fix up formatting and testing --- lib/peridiod/fwup_config.ex | 3 +-- lib/peridiod/socket.ex | 1 + lib/peridiod/update_manager.ex | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/peridiod/fwup_config.ex b/lib/peridiod/fwup_config.ex index 3aff5e4..84b940c 100644 --- a/lib/peridiod/fwup_config.ex +++ b/lib/peridiod/fwup_config.ex @@ -43,9 +43,8 @@ defmodule Peridiod.FwupConfig do update_available: update_available_fun } - def parse_fwup_env(%{} = fwup_env), do: Enum.to_list(fwup_env) - def parse_fwup_env([_] = fwup_env), do: fwup_env + def parse_fwup_env(fwup_env) when is_list(fwup_env), do: fwup_env @doc "Raises an ArgumentError on invalid arguments" @spec validate!(t()) :: t() diff --git a/lib/peridiod/socket.ex b/lib/peridiod/socket.ex index 3c37fed..61341ea 100644 --- a/lib/peridiod/socket.ex +++ b/lib/peridiod/socket.ex @@ -154,6 +154,7 @@ defmodule Peridiod.Socket do if socket.assigns.remote_iex do RemoteConsole.IEx.window_change(socket.assigns.remote_console_pid, width, height) end + {:ok, socket} end diff --git a/lib/peridiod/update_manager.ex b/lib/peridiod/update_manager.ex index d1e3d62..fefc85d 100644 --- a/lib/peridiod/update_manager.ex +++ b/lib/peridiod/update_manager.ex @@ -261,14 +261,15 @@ defmodule Peridiod.UpdateManager do @spec fwup_args(FwupConfig.t()) :: [String.t()] defp fwup_args(%FwupConfig{} = config) do - args = [ - "--apply", - "--no-unmount", - "-d", - config.fwup_devpath, - "--task", - config.fwup_task - ] ++ config.fwup_extra_args + args = + [ + "--apply", + "--no-unmount", + "-d", + config.fwup_devpath, + "--task", + config.fwup_task + ] ++ config.fwup_extra_args Enum.reduce(config.fwup_public_keys, args, fn public_key, args -> args ++ ["--public-key", public_key]