defmodule NervesHubLink.Configurator do
alias NervesHubLink.Backoff
alias __MODULE__.{Config, Default}
require Logger
@device_api_version "2.0.0"
@console_version "2.0.0"
defmodule Config do
defstruct connect: true,
data_path: "/data/nerves-hub",
device_api_host: nil,
device_api_port: 443,
device_api_sni: nil,
fwup_public_keys: [],
request_fwup_public_keys: false,
archive_public_keys: [],
fwup_devpath: "/dev/mmcblk0",
fwup_env: [],
nerves_key: [],
params: %{},
remote_iex: false,
socket: [],
ssl: []
@type t() :: %__MODULE__{
connect: boolean(),
data_path: Path.t(),
device_api_host: String.t(),
device_api_port: String.t(),
device_api_sni: charlist(),
fwup_public_keys: [binary()],
request_fwup_public_keys: boolean(),
archive_public_keys: [binary()],
fwup_devpath: Path.t(),
fwup_env: [{String.t(), String.t()}],
nerves_key: any(),
params: map(),
remote_iex: boolean(),
socket: any(),
ssl: [:ssl.tls_client_option()]
}
end
@callback build(%Config{}) :: Config.t()
@fwup_devpath "nerves_fw_devpath"
@spec build :: Config.t()
def build() do
Application.get_env(:nerves_hub_link, :configurator, fetch_default())
|> do_build()
|> add_socket_opts()
|> add_fwup_public_keys()
|> add_archive_public_keys()
end
defp add_socket_opts(config) do
# PhoenixClient requires these SSL options be passed as
# [transport_opts: [socket_opts: ssl]]. So for convenience,
# we'll bundle it all here as expected without overriding
# any other items that may have been provided in :socket or
# :transport_opts keys previously.
transport_opts = config.socket[:transport_opts] || []
transport_opts = Keyword.put(transport_opts, :socket_opts, config.ssl)
socket =
config.socket
|> Keyword.put(:transport_opts, transport_opts)
|> Keyword.put_new_lazy(:reconnect_after_msec, fn ->
# Default retry interval
# 1 second minimum delay that doubles up to 60 seconds. Up to 50% of
# the delay is added to introduce jitter into the retry attempts.
Backoff.delay_list(1000, 60000, 0.50)
end)
%{config | socket: socket}
end
defp base_config() do
base = struct(Config, Application.get_all_env(:nerves_hub_link))
url = "wss://#{base.device_api_host}:#{base.device_api_port}/socket/websocket"
socket = Keyword.put_new(base.socket, :url, url)
ssl =
base.ssl
|> Keyword.put_new(:verify, :verify_peer)
|> Keyword.put_new(:versions, [:"tlsv1.2"])
|> Keyword.put_new(
:server_name_indication,
to_charlist(base.device_api_sni || base.device_api_host)
)
fwup_devpath = Nerves.Runtime.KV.get(@fwup_devpath)
params =
Nerves.Runtime.KV.get_all_active()
|> Map.put("fwup_version", fwup_version())
|> Map.put("device_api_version", @device_api_version)
|> Map.put("console_version", @console_version)
%{base | params: params, socket: socket, ssl: ssl, fwup_devpath: fwup_devpath}
end
defp do_build(configurator) when is_atom(configurator) do
base_config()
|> configurator.build()
end
defp do_build({m, f, a}) when is_atom(m) and is_atom(f) and is_list(a) do
apply(m, f, [base_config() | a])
end
defp do_build(configurator) do
raise "[NervesHubLink] Bad Configurator - #{inspect(configurator)}"
end
defp fetch_default() do
if Code.ensure_loaded?(NervesKey) do
NervesHubLink.Configurator.NervesKey
else
Default
end
end
defp fwup_version do
{version_string, 0} = System.cmd("fwup", ["--version"])
String.trim(version_string)
end
defp add_fwup_public_keys(config) do
fwup_public_keys = for key <- config.fwup_public_keys, is_binary(key), do: key
if Enum.empty?(fwup_public_keys) || config.request_fwup_public_keys == true do
Logger.info("Requesting fwup public keys")
params = Map.put(config.params, "fwup_public_keys", "on_connect")
%{config | params: params}
else
%{config | fwup_public_keys: fwup_public_keys}
end
end
defp add_archive_public_keys(config) do
archive_public_keys = for key <- config.archive_public_keys, is_binary(key), do: key
if archive_public_keys == [] do
Logger.debug("""
No archive public keys were configured for nerves_hub_link.
This means that archive signatures are not being checked.
nerves_hub_link will fail to download archives.
""")
end
%{config | archive_public_keys: archive_public_keys}
end
end