diff --git a/.gitignore b/.gitignore index f29fbedc..c6232c61 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,6 @@ dump.rdb fwup-key.priv .env # secret config stuffs for dev environment. -config/auth_secret.exs +auth_secret.exs flash_fw.sh tmp diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..5ac86a24 --- /dev/null +++ b/TODO.md @@ -0,0 +1,26 @@ +# TODO + +## Serial +- [ ] FW updates. +- [ ] Autodetect TTY. (Or use Configuration?) + +## System +- [ ] OS updates. +- [ ] Key rotation for signed releases. + + +## Configuration +- [ ] Rewrite `configurator` web app. +- [ ] Only have configuration router up during configuration time. + +## CeleryScript +- [ ] Implement _EVERY_ CS Node again. + +## Logging +- [ ] Reimplement Logger + +## HTTP +- [ ] Write tests for HTTP. + +## Debug +- [ ] Implement `debug`/`developer` mode. diff --git a/config/config.exs b/config/config.exs index de93242f..c6257e59 100644 --- a/config/config.exs +++ b/config/config.exs @@ -26,36 +26,24 @@ config :fs, path: "/tmp/images" # Configure your our system. # Default implementation needs no special stuff. -config :farmbot, :init, [ - Farmbot.Bootstrap.Configurator -] +# See Farmbot.System.Supervisor and Farmbot.System.Init for details. +config :farmbot, :init, [] # Transports. -config :farmbot, :transport, [ - Farmbot.BotState.Transport.GenMqtt -] +config :farmbot, :transport, [] # Configure Farmbot Behaviours. config :farmbot, :behaviour, [ authorization: Farmbot.Bootstrap.Authorization, - - # uncomment this line if you have a real arduino plugged in. You will also need - # ensure the config for `:uart_handler` is correct. - # firmware_handler: Farmbot.Firmware.UartHandler, - firmware_handler: Farmbot.Host.FirmwareHandlerStub, - system_tasks: Farmbot.Host.SystemTasks ] -config :farmbot, :uart_handler, [ - tty: "/dev/ttyACM0" -] - -config :nerves_firmware_ssh, - authorized_keys: [ - File.read!(Path.join(System.user_home!, ".ssh/id_rsa.pub")) - ] - -config :bootloader, - init: [:nerves_runtime, :nerves_init_gadget], - app: :farmbot +case target do + "host" -> import_config("host/#{env}.exs") + _ -> + if File.exists?("config/#{target}/#{env}.exs") do + import_config("#{target}/#{env}.exs") + else + import_config("target/#{env}.exs") + end +end diff --git a/config/host/auth_secret_template.exs b/config/host/auth_secret_template.exs new file mode 100644 index 00000000..2246032e --- /dev/null +++ b/config/host/auth_secret_template.exs @@ -0,0 +1,10 @@ +use Mix.Config + +# You should copy this file to config/host/auth_secret.exs +# And make sure to configure the credentials to something that makes sense. + +config :farmbot, :authorization, [ + email: "admin@admin.com", + password: "password123", + server: "http://localhost:3000" +] diff --git a/config/host/auth_secret_test.exs b/config/host/auth_secret_test.exs new file mode 100644 index 00000000..8f15cd1d --- /dev/null +++ b/config/host/auth_secret_test.exs @@ -0,0 +1,12 @@ +use Mix.Config + +# You should copy this file to config/host/auth_secret.exs +# And make sure to configure the credentials to something that makes sense. + +config :farmbot, :authorization, [ + email: "admin@admin.com", + password: "password123", + server: "http://localhost:3000", + token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhZG1pbi5jb20iLCJpYXQiOjE1MDUwMDM1ODMsImp0aSI6ImIxZTM4ODJmLWJjYzMtNDcxNS04NTViLWVlZjBiZGQ0MTY0YSIsImlzcyI6Ii8vMTAuMTg2LjE5MC45MDozMDAwIiwiZXhwIjoxNTA4NDU5NTgzLCJtcXR0IjoiMTAuMTg2LjE5MC45MCIsIm9zX3VwZGF0ZV9zZXJ2ZXIiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2Zhcm1ib3QvZmFybWJvdF9vcy9yZWxlYXNlcy9sYXRlc3QiLCJmd191cGRhdGVfc2VydmVyIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9GYXJtYm90L2Zhcm1ib3QtYXJkdWluby1maXJtd2FyZS9yZWxlYXNlcy9sYXRlc3QiLCJib3QiOiJkZXZpY2VfMiJ9.kqsn8PIfReYRKlvqaIYsMZ5ai2TUP66ToqUX28V0Yt0xG7hY4eqBkLw8PWWgvCcx3J-xGr4wAKUdclkex-yZVU9L912eqFSrYSIkBKO4qUCS5Xtvc6MpdwI34r-JQckfoCs_G_PtTI1YNR8Rah8UyI4U8raIh_hjUmk5X0aWYxOqC9FgGfhtnR62RsYr1DzAKtf211g80VFUbQmE-9mIKBEVjPv5oiSzRo_FXovn_JaqG9JQkP-sabV4P9ItksJ9LNzwIZ_EYJCzM-838Ad6507CpbcpbVC6cN_WWKU0vP4L2Akvc4_0QLZ3TeY8UhgYqasPwcpOmvmzbyEJRFmZ1w" + +] diff --git a/config/host/dev.exs b/config/host/dev.exs new file mode 100644 index 00000000..bfe35feb --- /dev/null +++ b/config/host/dev.exs @@ -0,0 +1,28 @@ +use Mix.Config +unless File.exists?("config/host/auth_secret.exs") do + Mix.raise("You need to configure your dev environment. See `config/host/auth_secret.exs` for an example.\r\n") +end + +import_config("auth_secret.exs") + +# Configure your our system. +# Default implementation needs no special stuff. +config :farmbot, :init, [ +] + +# Transports. +config :farmbot, :transport, [ + Farmbot.BotState.Transport.GenMqtt +] + + +# Configure Farmbot Behaviours. +config :farmbot, :behaviour, [ + authorization: Farmbot.Bootstrap.Authorization, + + # uncomment this line if you have a real arduino plugged in. You will also need + # ensure the config for `:uart_handler` is correct. + # firmware_handler: Farmbot.Firmware.UartHandler, + firmware_handler: Farmbot.Host.FirmwareHandlerStub, + system_tasks: Farmbot.Host.SystemTasks +] diff --git a/config/host/test.exs b/config/host/test.exs new file mode 100644 index 00000000..2518adb3 --- /dev/null +++ b/config/host/test.exs @@ -0,0 +1,19 @@ +use Mix.Config + +unless File.exists?("config/host/auth_secret_test.exs") do + Mix.raise("You need to configure your test environment.\r\n") +end +import_config("auth_secret_test.exs") + +config :farmbot, :init, [] + +# Transports. +config :farmbot, :transport, [] + + +# Configure Farmbot Behaviours. +config :farmbot, :behaviour, [ + authorization: Farmbot.Test.Authorization, + firmware_handler: Farmbot.Host.FirmwareHandlerStub, + system_tasks: Farmbot.Test.SystemTasks +] diff --git a/config/target/dev.exs b/config/target/dev.exs new file mode 100644 index 00000000..a7d13a7c --- /dev/null +++ b/config/target/dev.exs @@ -0,0 +1,33 @@ +use Mix.Config + +# Configure your our system. +# Default implementation needs no special stuff. +config :farmbot, :init, [ + # Farmbot.Bootstrap.Configurator +] + +# Transports. +config :farmbot, :transport, [ + Farmbot.BotState.Transport.GenMqtt +] + + +# Configure Farmbot Behaviours. +config :farmbot, :behaviour, [ + authorization: Farmbot.Bootstrap.Authorization, + firmware_handler: Farmbot.Firmware.UartHandler, + system_tasks: Farmbot.Host.SystemTasks +] + +config :farmbot, :uart_handler, [ + tty: "/dev/ttyACM0" +] + +config :nerves_firmware_ssh, + authorized_keys: [ + File.read!(Path.join(System.user_home!, ".ssh/id_rsa.pub")) + ] + +config :bootloader, + init: [:nerves_runtime, :nerves_init_gadget], + app: :farmbot diff --git a/config/target/prod.exs b/config/target/prod.exs new file mode 100644 index 00000000..1a945ec6 --- /dev/null +++ b/config/target/prod.exs @@ -0,0 +1,9 @@ +use Mix.Config +import_config("dev.exs") + +config :nerves_firmware_ssh, + authorized_keys: [] + +config :bootloader, + init: [:nerves_runtime], + app: :farmbot diff --git a/lib/farmbot/bootstrap/authorization.ex b/lib/farmbot/bootstrap/authorization.ex index 92fc22fc..5cbd3d2e 100644 --- a/lib/farmbot/bootstrap/authorization.ex +++ b/lib/farmbot/bootstrap/authorization.ex @@ -72,4 +72,5 @@ defmodule Farmbot.Bootstrap.Authorization do end defp handle_error({:error, _reason} = error), do: error + defp handle_error({:error, :invalid, _} = error), do: error end diff --git a/lib/farmbot/bootstrap/configurator.ex b/lib/farmbot/bootstrap/configurator.ex deleted file mode 100644 index c33cb9c4..00000000 --- a/lib/farmbot/bootstrap/configurator.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Farmbot.Bootstrap.Configurator do - use GenServer - def start_link(_, opts) do - creds = [ - email: "connor@farmbot.io", - password: "password123", - server: "https://staging.farmbot.io" - ] - Application.put_env(:farmbot, :authorization, creds) - :ignore - end -end diff --git a/lib/farmbot/bootstrap/supervisor.ex b/lib/farmbot/bootstrap/supervisor.ex index 7d6103bf..d5e97625 100644 --- a/lib/farmbot/bootstrap/supervisor.ex +++ b/lib/farmbot/bootstrap/supervisor.ex @@ -98,11 +98,12 @@ defmodule Farmbot.Bootstrap.Supervisor do end end - defp actual_init(_args, email, pass, server) do - Logger.info "Beginning authorization: #{email} - #{server}" + defp actual_init(args, email, pass, server) do + Logger.info "Beginning authorization: #{@auth_task} - #{email} - #{server}" # get a token case @auth_task.authorize(email, pass, server) do {:ok, token} -> + Logger.info "Successful authorization: #{@auth_task} - #{email} - #{server}" children = [ supervisor(Farmbot.BotState.Supervisor, [token, [name: Farmbot.BotState.Supervisor ]]), supervisor(Farmbot.HTTP.Supervisor, [token, [name: Farmbot.HTTP.Supervisor]]), @@ -113,6 +114,9 @@ defmodule Farmbot.Bootstrap.Supervisor do # an application start fail and we would factory_reset from there, # the error message is just way more useful here. {:error, reason} -> Farmbot.System.factory_reset(reason) + # If we got invalid json, just try again. + # FIXME(Connor) Why does this happen? + {:error, :invalid, _} -> actual_init(args, email, pass, server) end end end diff --git a/lib/farmbot/firmware/handler.ex b/lib/farmbot/firmware/handler.ex deleted file mode 100644 index a11dda4c..00000000 --- a/lib/farmbot/firmware/handler.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Farmbot.Firmware.Handler do - @moduledoc "Handler for a firmware." -end diff --git a/scripts/build_firmware.sh b/scripts/build_firmware.sh deleted file mode 100755 index 1736e7ca..00000000 --- a/scripts/build_firmware.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -SYSTEM=$1 -export MIX_ENV=prod -export MIX_TARGET=$SYSTEM -echo "building firmware for $SYSTEM" -npm install -npm run build -mix deps.get -mix firmware diff --git a/scripts/build_release_images.sh b/scripts/build_release_images.sh deleted file mode 100755 index 57afd506..00000000 --- a/scripts/build_release_images.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Collects all the various images and archives for constructing a release. -SYSTEM=$1 -VERSION=$2 -export MIX_ENV=prod -export MIX_TARGET=$SYSTEM -REL_DIR=release-$VERSION -mkdir -p $REL_DIR - -FIRM_FILE=images/$MIX_ENV/$SYSTEM/farmbot.fw # the .fw file generated by `mix firmware` -FIRM_FILE_REL=$REL_DIR/farmbot-$SYSTEM-$VERSION.fw # where we want our formatted system -FIRM_FILE_REL_IMG=$REL_DIR/farmbot-$SYSTEM-$VERSION.img # same as the above file, but a .img file - -SYSTEM_DIR=nerves/NERVES_SYSTEM_$SYSTEM # the NERVES_SYSTEM build dir -SYSTEM_TAR=$SYSTEM_DIR/nerves_system_$SYSTEM.tar.gz # the archive generated by `make system` -SYSTEM_TAR_REL=$REL_DIR/farmbot.rootfs-$SYSTEM-$VERSION.tar.gz # where we want our outputted archive to be - -# copy the firmware file to the release file -cp $FIRM_FILE $FIRM_FILE_REL -# build an image from the release firmware -fwup -a -d $FIRM_FILE_REL_IMG -i $FIRM_FILE_REL -t complete -# copy the rootfs archive to the release dir -cp $SYSTEM_TAR $SYSTEM_TAR_REL diff --git a/scripts/build_system.sh b/scripts/build_system.sh deleted file mode 100755 index 0ffa9412..00000000 --- a/scripts/build_system.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -SYSTEM=$1 -echo "building system for $SYSTEM" -cd nerves/NERVES_SYSTEM_$SYSTEM -time make -make system diff --git a/scripts/clone_system.sh b/scripts/clone_system.sh deleted file mode 100755 index 1aa8b722..00000000 --- a/scripts/clone_system.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -SYSTEM=$1 -NERVES_SYSTEM_BR_GIT="https://github.com/nerves-project/nerves_system_br" -NERVES_SYSTEM_BR_COMMIT="049d8e19b69b0f84084182d9bdd915e4eb431ed5" - -if [ -d "nerves/NERVES_SYSTEM_$SYSTEM" ]; then - # Control will enter here if $DIRECTORY exists. - echo "NERVES_SYSTEM_$SYSTEM dir found." -else - echo "NERVES_SYSTEM_$SYSTEM dir not found." - CWD=$(pwd) - git clone $NERVES_SYSTEM_BR_GIT nerves/nerves_system_br - cd nerves/nerves_system_br - git checkout $NERVES_SYSTEM_BR_COMMIT - cd $CWD -fi - -nerves/nerves_system_br/create-build.sh nerves/nerves_system_$SYSTEM/nerves_defconfig nerves/NERVES_SYSTEM_$SYSTEM -if [ ! -f nerves/nerves_system_br/buildroot/.farmbot_applied_patches_list ]; then - echo "Applying FarmbotOS patches" - nerves/nerves_system_br/buildroot/support/scripts/apply-patches.sh nerves/nerves_system_br/buildroot patches - cp nerves/nerves_system_br/buildroot/.applied_patches_list nerves/nerves_system_br/buildroot/.farmbot_applied_patches_list - nerves/nerves_system_br/create-build.sh nerves/nerves_system_$SYSTEM/nerves_defconfig nerves/NERVES_SYSTEM_$SYSTEM -fi diff --git a/scripts/generate_makefile.exs b/scripts/generate_makefile.exs deleted file mode 100644 index 218562af..00000000 --- a/scripts/generate_makefile.exs +++ /dev/null @@ -1,117 +0,0 @@ -version = Path.join([__DIR__, "..", "VERSION"]) |> File.read! |> String.strip -IO.puts version - -{commitish, _} = System.cmd("git", ["log", "--pretty=format:%hQQ%adQQ%f", "-1"]) -thing = String.split(commitish, "QQ") - -IO.puts "Finding private key." -priv_key_path = System.get_env("PRIV_KEY_FILE") || nil - -if priv_key_path do - IO.puts("Found private key!") -else - IO.puts("Could not find private key!") -end - -IO.puts "Building default portions" -initial = "# THIS FILE WAS GENERATED BY `build_makefile.exs` -# #{Enum.join(thing, "\n# ")} - -default: rpi3 - -ifdef SILENT -.SILENT: -endif - -dev_env: -\texport MIX_ENV=dev - -prod_env: -\texport MIX_ENV=prod - -clean: -\t$(info Cleaning) -\trm -rf nerves/NERVES_SYSTEM_* -\trm -rf nerves/nerves_system_br -\trm -rf npm-debug* -\trm -rf erl_crash.dump -\trm -rf doc -\trm -rf cover -\trm -rf deps -\trm -rf node_modules -\trm -rf cache -\trm -rf _build -\trm -rf images -\trm -rf images -\trm -rf latest-release -\trm Makefile -\telixir scripts/generate_makefile.exs - -test: dev_env -\tscripts/run_tests.sh - -travis_test: dev_env -\tscripts/run_travis_tests.sh - -## End default portion.\n" - -build_system_part = fn(sys) -> -"\n## begin #{sys} portion. - -## #{sys} env -env-#{sys}: prod_env -\texport NERVES_TARGET=#{sys} - -## #{sys} build -#{sys}: env-#{sys} system-#{sys} firmware-#{sys} -\t$(info Building stuff for #{sys}) - -## #{sys} create-build -create-build-#{sys}: -\tscripts/clone_system.sh #{sys} - -## #{sys} system -system-#{sys}: create-build-#{sys} -\t$(info Building Linux System for #{sys}) -\tscripts/build_system.sh #{sys} - -## #{sys} firmware -firmware-#{sys}: -\t$(info Building Firmware for #{sys}) -\tscripts/build_firmware.sh #{sys} - -release-#{sys}: #{sys} -\tscripts/build_release_images.sh #{sys} #{version} -#{if priv_key_path, do: "\tscripts/sign_release.sh #{sys} #{version} #{priv_key_path}"} - -## end #{sys} portion." -end - -list = File.ls!("nerves") -only_systems = Enum.reduce(list, [], fn(d, acc) -> - case d do - # ignore nerves_system_br - "nerves_system_br" -> acc - "nerves_system_"<> sys -> [sys | acc] - _ -> acc - end -end) - -initial_mod = Enum.reduce(only_systems, initial, fn(sys, acc) -> - IO.puts "Defining system: #{sys}" - acc <> build_system_part.(sys) -end) - -IO.puts "Defining release for #{version}" -final = initial_mod <> -# "\n\n## Release will build all the systems. -# release: clean #{Enum.map(only_systems, fn(a) -> " release-"<>a end)}" -"\n\n## Release will build all the systems. -release: clean release-rpi3" - -IO.puts "Writing file." -File.write("Makefile", final) -blah = "release-#{version}" -IO.puts blah -File.rm "./release-latest" -File.ln_s(blah, "./release-latest") diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh deleted file mode 100755 index 75d59803..00000000 --- a/scripts/run_tests.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export MIX_ENV=test -mix deps.get -mix all_test diff --git a/scripts/run_travis_tests.sh b/scripts/run_travis_tests.sh deleted file mode 100755 index ecb95620..00000000 --- a/scripts/run_travis_tests.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -export MIX_ENV=test -mix deps.get -mix travis_test || { echo 'Tests failed!' ; exit 1; } -if [[ "$TRAVIS_BRANCH" == "staging" ]]; then - SILENT=true make release -fi diff --git a/scripts/sign_release.sh b/scripts/sign_release.sh deleted file mode 100755 index 866bf352..00000000 --- a/scripts/sign_release.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -SYSTEM=$1 -VERSION=$2 -PRIV_KEY_PATH=$3 -REL_DIR=release-$VERSION -FIRM_FILE_REL=$REL_DIR/farmbot-$SYSTEM-$VERSION.fw -SIGNED_FIRM_FILE_REL=$REL_DIR/farmbot-$SYSTEM-$VERSION-signed.fw - -MIX_ENV=prod MIX_TARGET=$SYSTEM mix firmware.sign $PRIV_KEY_PATH $SIGNED_FIRM_FILE_REL -mv $FIRM_FILE_REL $FIRM_FILE_REL.unsigned -mv $SIGNED_FIRM_FILE_REL $FIRM_FILE_REL diff --git a/scripts/tty0tty.sh b/scripts/tty0tty.sh deleted file mode 100755 index 46b9077e..00000000 --- a/scripts/tty0tty.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -CWD=`pwd` -mkdir /tmp/tty0tty -cd /tmp/tty0tty -git clone https://github.com/freemed/tty0tty . -cd module -make -sudo cp tty0tty.ko /lib/modules/$(uname -r)/kernel/drivers/misc/ -sudo depmod -sudo modprobe tty0tty -sudo chmod 666 /dev/tnt*; -cd $CWD diff --git a/test/support/test_support.ex b/test/support/test_support.ex index a926fe7d..f276afff 100644 --- a/test/support/test_support.ex +++ b/test/support/test_support.ex @@ -1,6 +1,7 @@ defmodule FarmbotTestSupport do @moduledoc "Test Helpers." import Farmbot.DebugLog, only: [color: 1] + require Logger defp error(err) do """ @@ -16,6 +17,7 @@ defmodule FarmbotTestSupport do end def preflight_checks do + Logger.info "Starting Preflight Checks." with {:ok, tkn} <- ping_api(), :ok <- ping_mqtt(tkn) do :ok @@ -28,14 +30,18 @@ defmodule FarmbotTestSupport do server = Application.get_env(:farmbot, :authorization)[:server] email = Application.get_env(:farmbot, :authorization)[:email] password = Application.get_env(:farmbot, :authorization)[:password] + Logger.info "Preflight check: api: #{server}" case Farmbot.Bootstrap.Authorization.authorize(email, password, server) do {:error, _reason} -> :api - {:ok, tkn} -> {:ok, tkn} + {:ok, tkn} -> + Logger.info "Preflight check api complete." + {:ok, tkn} end end defp ping_mqtt(tkn) do url = Farmbot.Jwt.decode!(tkn).mqtt + Logger.info "Preflight check: mqtt: #{url}" case :gen_tcp.connect(to_charlist(url), 1883, [:binary]) do {:error, _} -> :mqtt {:ok, port} -> :gen_tcp.close(port)