start working out configurator

pull/363/head
Connor Rigby 2017-10-03 13:07:17 -07:00
parent 05c2c4cb61
commit 4c40d288d7
6 changed files with 172 additions and 69 deletions

View File

@ -14,9 +14,9 @@ config :farmbot, Farmbot.System.ConfigStorage,
config :farmbot, data_path: "/root"
# Configure your our system.
# Default implementation needs no special stuff.
# Configure your our init system.
config :farmbot, :init, [
# Allows for first boot configuration.
Farmbot.Target.Bootstrap.Configurator,
# Start up Network

View File

@ -1,57 +0,0 @@
# Additional configuration for erlinit
# Turn on the debug prints
#-v
# Specify where erlinit should send the IEx prompt. Only one may be enabled at
# a time.
-c ttyS0 # UART pins on the GPIO connector
# -c tty1 # HDMI output
# If more than one tty are available, always warn if the user is looking at the
# wrong one.
--warn-unused-tty
# Use dtach to capture the iex session so that it can be redirected to the
# app's GUI
#-s "/usr/bin/dtach -N /tmp/iex_prompt"
# Specify the user and group IDs for the Erlang VM
#--uid 100
#--gid 200
# Uncomment to hang the board rather than rebooting when Erlang exits
# NOTE: Do not enable on production boards
--hang-on-exit
# Change the graceful shutdown time. If 10 seconds isn't long enough between
# calling "poweroff", "reboot", or "halt" and :init.stop/0 stopping all OTP
# applications, enable this option with a new timeout in milliseconds.
#--graceful-shutdown-timeout 15000
# Optionally run a program if the Erlang VM exits
#--run-on-exit /bin/sh
# Enable UTF-8 filename handling in Erlang and custom inet configuration
-e LANG=en_US.UTF-8;LANGUAGE=en;ERL_INETRC=/etc/erl_inetrc;ERL_CRASH_DUMP=/root/crash.dump
# Mount the application partition
# NOTE: This must match the location in the fwup.conf. If it doesn't the system
# will probably still work fine, but you won't get shell history since
# bootloader/nerves_runtime can't mount the application filesystem before
# the history is loaded. If this mount fails due to corruption, etc.,
# nerves_runtime will auto-format it. Your applications will need to handle
# initializing any expected files and folders.
-m /dev/mmcblk0p3:/root:ext4::
# Erlang release search path
-r /srv/erlang
# Assign a unique hostname based on the board id
-d "/usr/bin/boardid -b rpi -n 4"
-n nerves-%.4s
# If using bootloader (https://github.com/nerves-project/bootloader), start the
# bootloader OTP release up first. If bootloader isn't around, erlinit fails back
# to the main OTP release.
--boot bootloader

View File

@ -104,6 +104,7 @@ defmodule Farmbot.Mixfile do
{:nerves_runtime, "~> 0.4"},
{:nerves_network, "~> 0.3"},
{:nerves_firmware_ssh, "~> 0.2"},
{:dhcp_server, "~> 0.1.3"}
]
end

View File

@ -6,6 +6,7 @@
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [], [], "hexpm"},
"dhcp_server": {:hex, :dhcp_server, "0.1.3", "9423311a7aa25bf517f5504f085a7d69e7dc98e98cc123ee77f5e8ae4fadc701", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network, ">= 0.0.0", [hex: :nerves_network, repo: "hexpm", optional: true]}], "hexpm"},
"distillery": {:hex, :distillery, "1.5.1", "7ad7354214959c0f65f57ddd49478c81c3b2733ca2e5ccfb9eb55351108466aa", [], [], "hexpm"},
"dns": {:hex, :dns, "1.0.1", "1d88187fdf564d937cee202949141090707fd0c9d7fcae903a6878ef24ef5d1e", [], [{:socket, "~> 0.3.12", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"},
"ecto": {:hex, :ecto, "2.2.4", "defde3c8eca385bd86466d2e1491d19e77f9b79ad996dc8e89e4e107f3942f40", [], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
@ -28,6 +29,7 @@
"nerves_network_interface": {:hex, :nerves_network_interface, "0.4.2", "7a3663a07803f2f9f1e37146714d24ccec1e9349268586e4ed8c41f38641d837", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_runtime": {:hex, :nerves_runtime, "0.4.4", "26034bc7d13dbd46aab2f429f988656621a4d91872ccf5fa748c16630bd65016", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.5", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_system_br": {:hex, :nerves_system_br, "0.13.8", "bca89f31ef27ddad48feb30de648913f0091e205652d005b95255c49e743d087", [], [], "hexpm"},
"nerves_system_farmbot_rpi3": {:hex, :nerves_system_farmbot_rpi3, "0.16.2-farmbot", "1e913b6360bdcac8e9e11b22f63da8c48cb6cd1f9a6ba5c8aa43a44121693166", [], [{:nerves, "~> 0.7", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "~> 0.13.7", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "~> 0.11.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_system_rpi3": {:hex, :nerves_system_rpi3, "0.16.1", "46b2fc942d7434d9eb8164c06c25a94ee6dd6db7e88c857101e5a06e6b7d01a8", [], [{:nerves, "~> 0.7", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "~> 0.13.7", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "~> 0.11.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "0.11.0", "8d7606275a2d19de26ae238cd59475f4c06679aa9222b8987518d7c8a7beae51", [], [{:nerves, "~> 0.7", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.1", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.1.0", "0f03e4a3f3beef5fe271de0148b9f106c417e57f303f635c21c74b4bd6eb68ee", [], [], "hexpm"},
@ -48,4 +50,4 @@
"tzdata": {:hex, :tzdata, "0.1.201605", "0c4184819b9d6adedcc02107b68321c45d8e853def7a32629b7961b9f2e95f33", [], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [], [], "hexpm"},
"vmq_commons": {:hex, :vmq_commons, "1.0.0", "5f5005c12db33f92f40e818a3617fb148972d59adcf99298c9d3808ef3582e34", [], [], "hexpm"}}
"vmq_commons": {:git, "https://github.com/farmbot-labs/vmq_commons.git", "e780a297d5807a3537723d590036d37abce07cc5", []}}

View File

@ -22,14 +22,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator do
def start_link(_, opts) do
Logger.info "Configuring Farmbot."
supervisor = Supervisor.start_link(__MODULE__, [self()], opts)
# case supervisor do
# {:ok, pid} ->
# receive do
# :ok -> stop(pid, :ignore)
# {:error, _reason} = err -> stop(pid, err)
# end
# :ignore -> :ignore
# end
case supervisor do
{:ok, pid} ->
receive do
:ok -> stop(pid, :ignore)
{:error, _reason} = err -> stop(pid, err)
end
:ignore -> :ignore
end
end
def init(cb) do
@ -39,7 +39,8 @@ defmodule Farmbot.Target.Bootstrap.Configurator do
import Supervisor.Spec
:ets.new(:session, [:named_table, :public, read_concurrency: true])
children = [
Plug.Adapters.Cowboy.child_spec(:http, Farmbot.Target.Bootstrap.Configurator.Router, [], [port: 4001])
Plug.Adapters.Cowboy.child_spec(:http, Farmbot.Target.Bootstrap.Configurator.Router, [], [port: 80]),
worker(Farmbot.Target.Bootstrap.Configurator.CaptivePortal, [cb])
]
opts = [strategy: :one_for_one]
supervise(children, opts)

View File

@ -0,0 +1,156 @@
defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
defmodule Hostapd do
@moduledoc """
Manages an OS process of hostapd.
"""
defmodule State do
@moduledoc false
defstruct [:hostapd, :interface]
end
use GenServer
require Logger
@hostapd_conf_file "hostapd.conf"
@hostapd_pid_file "hostapd.pid"
@doc ~s"""
Example:
Iex> Hostapd.start_link ip_address: "192.168.24.1",
...> manager: Farmbot.Network.Manager, interface: "wlan0"
"""
def start_link(opts, gen_server_opts \\ []) do
GenServer.start_link(__MODULE__, opts, gen_server_opts)
end
def init(opts) do
# We want to know if something does.
Process.flag :trap_exit, true
interface = Keyword.fetch!(opts, :interface)
Logger.info ">> is starting hostapd on #{interface}"
{hostapd_port, hostapd_os_pid} = setup_hostapd(interface, "192.168.24.1")
state = %State{
hostapd: {hostapd_port, hostapd_os_pid},
interface: interface,
}
{:ok, state}
end
defp setup_hostapd(interface, ip_addr) do
# Make sure the interface is in proper condition.
:ok = hostapd_ip_settings_up(interface, ip_addr)
# build the hostapd configuration
hostapd_conf = build_hostapd_conf(interface, build_ssid())
# build a config file
File.mkdir! "/tmp/hostapd"
File.write! "/tmp/hostapd/#{@hostapd_conf_file}", hostapd_conf
hostapd_cmd = "hostapd -P /tmp/hostapd/#{@hostapd_pid_file} " <>
"/tmp/hostapd/#{@hostapd_conf_file}"
hostapd_port = Port.open({:spawn, hostapd_cmd}, [:binary])
hostapd_os_pid = hostapd_port|> Port.info() |> Keyword.get(:os_pid)
{hostapd_port, hostapd_os_pid}
end
defp hostapd_ip_settings_up(interface, ip_addr) do
:ok =
"ip" |> System.cmd(["link", "set", "#{interface}", "up"])
|> print_cmd
:ok =
"ip" |> System.cmd(["addr", "add", "#{ip_addr}/24", "dev", "#{interface}"])
|> print_cmd
:ok
end
defp hostapd_ip_settings_down(interface, ip_addr) do
:ok =
"ip" |> System.cmd(["link", "set", "#{interface}", "down"])
|> print_cmd
:ok =
"ip" |> System.cmd(["addr", "del", "#{ip_addr}/24", "dev", "#{interface}"])
|> print_cmd
:ok =
"ip" |> System.cmd(["link", "set", "#{interface}", "up"])
|> print_cmd
:ok
end
defp build_hostapd_conf(interface, ssid) do
"""
interface=#{interface}
ssid=#{ssid}
hw_mode=g
channel=6
auth_algs=1
wmm_enabled=0
"""
end
defp build_ssid do
node_str =
node() |> Atom.to_string
[name, "farmbot-" <> id] =
node_str |> String.split("@")
name <> "-" <> id
end
defp kill(os_pid),
do: :ok = "kill" |> System.cmd(["15", "#{os_pid}"]) |> print_cmd
defp print_cmd({_, 0}), do: :ok
defp print_cmd({res, num}) do
Logger.error ">> encountered an error (#{num}): #{res}"
:error
end
def handle_info({port, {:data, data}}, state) do
{hostapd_port, _} = state.hostapd
cond do
port == hostapd_port ->
handle_hostapd(data, state)
true -> {:noreply, state}
end
end
def handle_info(_, state), do: {:noreply, state}
defp handle_hostapd(data, state) when is_bitstring(data) do
String.trim(data) |> Logger.debug()
{:noreply, state}
end
defp handle_hostapd(_, state), do: {:noreply, state}
def terminate(_, state) do
Logger.info ">> is stopping hostapd"
{_hostapd_port, hostapd_pid} = state.hostapd
:ok = kill(hostapd_pid)
hostapd_ip_settings_down(state.interface, state.ip_addr)
File.rm_rf! "/tmp/hostapd"
end
end
use GenServer
require Logger
@interface "wlan0"
def start_link(cb) do
GenServer.start_link(__MODULE__, [cb], name: __MODULE__)
end
def init([cb]) do
Logger.debug "Starting captive portal."
{:ok, hostapd} = Hostapd.start_link(interface: @interface)
{:ok, dhcp_server} = DHCPServer.start_link(interface: @interface)
{:ok, %{callback: cb, hostapd: hostapd, hcp_server: dhcp_server}}
end
def terminate(_, state) do
Logger.debug "Stopping captive portal."
GenServer.stop(state.hostapd, :normal)
GenServer.stop(state.dhcp_server, :normal)
send state.callback, :ok
end
end