Nerves Hub (#647)

pull/667/head
Connor Rigby 2018-11-21 16:20:11 -08:00
parent 15ad5dc4ab
commit a23d223995
44 changed files with 8901 additions and 896 deletions

View File

@ -1,7 +1,7 @@
version: 2.0 version: 2.0
defaults: &defaults defaults: &defaults
docker: docker:
- image: nervesproject/nerves_system_br:1.4.2 - image: nervesproject/nerves_system_br:1.5.2
install_elixir: &install_elixir install_elixir: &install_elixir
run: run:
@ -38,136 +38,182 @@ install_slack_helpers: &install_slack_helpers
command: | command: |
wget https://gist.githubusercontent.com/ConnorRigby/03e722be4be70f8588f5ed74420e4eaa/raw/28a51d8f52ec7d569e8f7f20b83349816ddf63cf/slack_message.ex wget https://gist.githubusercontent.com/ConnorRigby/03e722be4be70f8588f5ed74420e4eaa/raw/28a51d8f52ec7d569e8f7f20b83349816ddf63cf/slack_message.ex
install_ghr: &install_ghr
run:
name: Install ghr (Github Releases)
command: |
wget https://github.com/tcnksm/ghr/releases/download/v0.9.0/ghr_v0.9.0_linux_amd64.tar.gz
tar xf ghr_v0.9.0_linux_amd64.tar.gz
ln -sf ghr_v0.9.0_linux_amd64/ghr .
install_jq: &install_jq
run:
name: Install jq
command: |
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
chmod +x ./jq-linux64
jobs: jobs:
test: test:
<<: *defaults <<: *defaults
environment: environment:
MIX_ENV: test MIX_ENV: test
MIX_TARGET: host MIX_TARGET: host
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes" ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps: steps:
- checkout - checkout
- run: git submodule update --init --recursive - run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- <<: *install_elixir - <<: *install_elixir
- restore_cache:
keys:
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
- <<: *install_arduino
- <<: *install_hex_archives - <<: *install_hex_archives
- restore_cache: - run:
keys: name: Test Farmbot OS
- v6-dep-cache-{{ checksum "mix.lock.host" }} command: |
- <<: *fetch_and_compile_deps mix deps.get
mix compile
- save_cache: - save_cache:
key: v6-dep-cache-{{ checksum "mix.lock.host" }} key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
paths: paths:
- _build/host - _build/host
- _build/arduino
- deps/host - deps/host
- save_cache: build_rpi3_prod_firmware:
key: v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
paths:
- ~/arduino-1.8.5
- run:
name: "Credo code static code analysis"
command: mix credo
- run:
name: "Elixir Code formatter"
command: mix format --check-formatted
- run:
command: mix coveralls.circle --exclude farmbot_firmware
firmware_dev:
<<: *defaults <<: *defaults
environment: environment:
MIX_TARGET: rpi3
MIX_ENV: dev
ENV: CI
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
steps:
- checkout
- run: git submodule update --init --recursive
- <<: *install_elixir
- restore_cache:
keys:
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
- <<: *install_arduino
- restore_cache:
keys:
- v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- <<: *install_hex_archives
- <<: *fetch_and_compile_deps
- run: mix firmware
- save_cache:
key: v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
paths:
- _build/rpi3
- _build/host
- _build/arduino
- deps/rpi3
- deps/host
- ~/.nerves
- run: mkdir -p artifacts
- run:
name: Decode fwup priv key
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
- run:
name: Sign firmware
command: fwup -S -s $NERVES_FW_PRIV_KEY -i _build/${MIX_TARGET}/${MIX_ENV}/nerves/images/farmbot.fw -o artifacts/farmbot-${MIX_TARGET}-$(cat VERSION)-beta.fw
- save_cache:
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ./artifacts
firmware_beta:
<<: *defaults
environment:
MIX_TARGET: rpi3
MIX_ENV: prod MIX_ENV: prod
ENV: CI MIX_TARGET: rpi3
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes" ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps: steps:
- checkout - checkout
- run: git submodule update --init --recursive - run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- <<: *install_elixir - <<: *install_elixir
- restore_cache:
keys:
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
- <<: *install_arduino
- <<: *install_hex_archives - <<: *install_hex_archives
- restore_cache: - run:
keys: name: Build Farmbot OS Firmware
- v6-dependency-cache-{{ checksum "mix.lock.rpi3" }} command: |
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix deps.get mix deps.get
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix compile mix compile --force
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix firmware mix firmware
- run:
name: Create artifact dir
command: mkdir -p /nerves/deploy/system/artifacts
- run:
name: Create artifacts
command: |
cp _build/rpi3/prod/nerves/images/farmbot.fw /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
- save_cache: - save_cache:
key: v6-dependency-cache-{{ checksum "mix.lock.rpi3" }} key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
paths: paths:
- _build/rpi3 - _build/rpi3/
- _build/host
- _build/arduino
- deps/rpi3 - deps/rpi3
- deps/host - ~/.nerves/
- ~/.nerves - store_artifacts:
- run: mkdir -p artifacts path: /nerves/deploy/system/artifacts
- run: destination: images
name: Decode fwup priv key
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
- run:
name: Sign firmware
command: MIX_ENV=prod MIX_TARGET=rpi3 fwup -S -s $NERVES_FW_PRIV_KEY -i _build/${MIX_TARGET}/${MIX_ENV}/nerves/images/farmbot.fw -o artifacts/farmbot-${MIX_TARGET}-$(cat VERSION)-beta.fw
- save_cache: - save_cache:
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }} key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
paths: paths:
- ./artifacts - "/nerves/deploy/system"
deploy_rpi3_prod_firmware_master:
deploy_beta_firmware:
<<: *defaults <<: *defaults
environment:
MIX_ENV: prod
MIX_TARGET: rpi3
ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps: steps:
- checkout - checkout
- run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- restore_cache:
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- <<: *install_elixir - <<: *install_elixir
- <<: *install_hex_archives
- <<: *install_ghr
- run:
name: Sign Image
command: mix nerves_hub.firmware sign --key prod /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
- run:
name: Publish to NervesHub
command: mix nerves_hub.firmware publish --deploy prod-stable /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
deploy_rpi3_prod_firmware_beta:
<<: *defaults
environment:
MIX_ENV: prod
MIX_TARGET: rpi3
ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps:
- checkout
- run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- restore_cache:
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- <<: *install_elixir
- <<: *install_hex_archives
- run:
name: Sign Image
command: mix nerves_hub.firmware sign --key prod /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
- run:
name: Publish to NervesHub
command: mix nerves_hub.firmware publish --deploy prod-beta /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
deploy_rpi3_prod_firmware_staging:
<<: *defaults
environment:
MIX_ENV: prod
MIX_TARGET: rpi3
ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps:
- checkout
- run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- restore_cache:
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- <<: *install_elixir
- <<: *install_hex_archives
- run:
name: Sign Image
command: mix nerves_hub.firmware sign --key staging /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
- run:
name: Publish to NervesHub
command: mix nerves_hub.firmware publish --deploy prod-staging --ttl 3600 /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
publish_rpi3_prod_firmware_beta_release:
<<: *defaults
environment:
MIX_ENV: prod
MIX_TARGET: rpi3
ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps:
- checkout
- run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- restore_cache:
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- <<: *install_elixir
- <<: *install_hex_archives
- <<: *install_ghr
- <<: *install_slack_helpers - <<: *install_slack_helpers
- <<: *install_jq
- run: - run:
name: Run setup script name: Run setup script
command: bash .circleci/setup-heroku.sh command: bash .circleci/setup-heroku.sh
@ -175,18 +221,16 @@ jobs:
fingerprints: fingerprints:
- "97:92:32:5d:d7:96:e1:fa:f3:6b:f3:bd:d6:aa:84:c6" - "97:92:32:5d:d7:96:e1:fa:f3:6b:f3:bd:d6:aa:84:c6"
- run: - run:
name: Install dependencies name: Sign Firmware
command: | command: |
wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
unzip ghr_v0.5.4_linux_amd64.zip mv /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 fwup -S -s $NERVES_FW_PRIV_KEY -i /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw -o /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION)-beta.fw
chmod +x ./jq-linux64
- run: - run:
command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES
- restore_cache:
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
- run: - run:
command: ./ghr -t $GITHUB_TOKEN -u farmbot -r farmbot_os -recreate -prerelease -b "$(cat RELEASE_NOTES)" -c $(git rev-parse --verify HEAD) "v$(cat VERSION)-beta" $PWD/artifacts name: Publish Github Release
command: ./ghr -t $GITHUB_TOKEN -u farmbot -r farmbot_os -prerelease -recreate -prerelease -b "$(cat RELEASE_NOTES)" -c $(git rev-parse --verify HEAD) "v$(cat VERSION)-beta" /nerves/deploy/system/artifacts/
- run: - run:
name: Update heroku env name: Update heroku env
command: | command: |
@ -195,122 +239,171 @@ jobs:
heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-production heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-production
heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-staging heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-staging
elixir slack_message.ex $SLACK_MESSAGE elixir slack_message.ex $SLACK_MESSAGE
publish_rpi3_prod_firmware_master_release:
firmware_prod:
<<: *defaults <<: *defaults
environment: environment:
MIX_TARGET: rpi3
MIX_ENV: prod MIX_ENV: prod
ENV: CI MIX_TARGET: rpi3
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes" ELIXIR_VERSION: 1.7.3
SKIP_ARDUINO_BUILD: 1
steps: steps:
- checkout - checkout
- run: git submodule update --init --recursive - run: git submodule update --init --recursive
- restore_cache:
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- restore_cache:
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
- restore_cache:
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- <<: *install_elixir - <<: *install_elixir
- restore_cache:
keys:
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
- <<: *install_arduino
- <<: *install_hex_archives - <<: *install_hex_archives
- restore_cache: - <<: *install_ghr
keys: - <<: *install_slack_helpers
- v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- run: mix deps.get
- run: mix compile
- run: mix firmware
- save_cache:
key: v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
paths:
- _build/rpi3
- _build/host
- _build/arduino
- deps/rpi3
- deps/host
- ~/.nerves
- run: mkdir -p artifacts
- run: - run:
name: Decode fwup priv key name: Run setup script
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY command: bash .circleci/setup-heroku.sh
- add_ssh_keys:
fingerprints:
- "97:92:32:5d:d7:96:e1:fa:f3:6b:f3:bd:d6:aa:84:c6"
- run: - run:
name: Sign firmware name: Sign Firmware
command: fwup -S -s $NERVES_FW_PRIV_KEY -i _build/${MIX_TARGET}/${MIX_ENV}/nerves/images/farmbot.fw -o artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
- run:
name: Create img
command: mix firmware.image artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).img
- save_cache:
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ./artifacts
deploy_prod_firmware:
<<: *defaults
steps:
- checkout
- run:
name: Install dependencies
command: | command: |
wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
unzip ghr_v0.5.4_linux_amd64.zip mv /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 fwup -S -s $NERVES_FW_PRIV_KEY -i /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw -o /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw
chmod +x ./jq-linux64 - run:
name: Create Image file
command: fwup -a -t complete -i /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw -d /nerves/deploy/system/farmbot-${MIX_TARGET}-$(cat VERSION).img
- run: - run:
command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES
- restore_cache:
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
- run: - run:
command: ./ghr -t $GITHUB_TOKEN -u farmbot -r farmbot_os -recreate -prerelease -draft -delete -b "$(cat RELEASE_NOTES)" -c $(git rev-parse --verify HEAD) "v$(cat VERSION)" $PWD/artifacts name: Publish Github Release
command: ./ghr -t $GITHUB_TOKEN -u farmbot -r farmbot_os -recreate -prerelease -draft -delete -b "$(cat RELEASE_NOTES)" -c $(git rev-parse --verify HEAD) "v$(cat VERSION)" /nerves/deploy/system/
- run:
name: Send Slack Message
command: elixir slack_message.ex "New Farmbot Prod release $(cat VERSION)"
workflows: workflows:
version: 2 version: 2
test_firmware_upload: test:
jobs: jobs:
- test: - test:
context: org-global context: org-global
filters: filters:
branches: branches:
ignore: ignore:
# Merging is blocked on these branches until tests pass.
- beta - beta
- master - master
- firmware_dev: - staging
context: org-global deploy_stable_production:
requires:
- test
filters:
branches:
ignore:
- beta
- master
deploy_beta:
jobs: jobs:
- firmware_beta: - build_rpi3_prod_firmware:
context: org-global context: farmbot-production
filters: filters:
branches: branches:
only: only:
- beta - master
- deploy_beta_firmware: - deploy_rpi3_prod_firmware_master:
context: org-global context: farmbot-production
filters: filters:
branches: branches:
only: only:
- beta - master
requires: requires:
- firmware_beta - build_rpi3_prod_firmware
- publish_rpi3_prod_firmware_master_release:
deploy_prod: context: org-global
filters:
branches:
only:
- master
requires:
- build_rpi3_prod_firmware
deploy_stable_staging:
jobs: jobs:
- firmware_prod: - build_rpi3_prod_firmware:
context: org-global context: farmbot-staging
filters: filters:
branches: branches:
only: only:
- master - master
- deploy_prod_firmware: - deploy_rpi3_prod_firmware_master:
context: org-global context: farmbot-staging
filters: filters:
branches: branches:
only: only:
- master - master
requires: requires:
- firmware_prod - build_rpi3_prod_firmware
deploy_beta_production:
jobs:
- build_rpi3_prod_firmware:
context: farmbot-production
filters:
branches:
only:
- beta
- deploy_rpi3_prod_firmware_beta:
context: farmbot-production
filters:
branches:
only:
- beta
requires:
- build_rpi3_prod_firmware
- publish_rpi3_prod_firmware_beta_release:
context: org-global
filters:
branches:
only:
- beta
requires:
- build_rpi3_prod_firmware
deploy_beta_staging:
jobs:
- build_rpi3_prod_firmware:
context: farmbot-staging
filters:
branches:
only:
- beta
- deploy_rpi3_prod_firmware_beta:
context: farmbot-staging
filters:
branches:
only:
- beta
requires:
- build_rpi3_prod_firmware
deploy_staging_production:
jobs:
- build_rpi3_prod_firmware:
context: farmbot-production
filters:
branches:
only:
- staging
- deploy_rpi3_prod_firmware_staging:
context: farmbot-production
filters:
branches:
only:
- staging
requires:
- build_rpi3_prod_firmware
deploy_staging_staging:
jobs:
- build_rpi3_prod_firmware:
context: farmbot-staging
filters:
branches:
only:
- staging
- deploy_rpi3_prod_firmware_staging:
context: farmbot-staging
filters:
branches:
only:
- staging
requires:
- build_rpi3_prod_firmware

2
.gitignore vendored
View File

@ -57,4 +57,4 @@ RELEASE_NOTES
.elixir_ls .elixir_ls
nerves-hub nerves-hub
*.pem *.pem

View File

@ -69,7 +69,11 @@ config :farmbot,
global_overlay_dir = "rootfs_overlay" global_overlay_dir = "rootfs_overlay"
config :nerves, :firmware, rootfs_overlay: [global_overlay_dir] config :nerves, :firmware,
rootfs_overlay: [global_overlay_dir],
provisioning: :nerves_hub
import_config("nerves_hub.exs")
case target do case target do
"host" -> "host" ->
@ -82,5 +86,7 @@ case target do
do: import_config("target/#{target}.exs") do: import_config("target/#{target}.exs")
if File.exists?(custom_rootfs_overlay_dir), if File.exists?(custom_rootfs_overlay_dir),
do: config :nerves, :firmware, rootfs_overlay: [global_overlay_dir, custom_rootfs_overlay_dir] do: config :nerves, :firmware,
rootfs_overlay: [global_overlay_dir, custom_rootfs_overlay_dir],
provisioning: :nerves_hub
end end

View File

@ -45,8 +45,15 @@ config :farmbot, default_server: "https://staging.farm.bot"
config :farmbot, :behaviour, [ config :farmbot, :behaviour, [
authorization: Farmbot.Bootstrap.Authorization, authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Host.SystemTasks, system_tasks: Farmbot.Host.SystemTasks,
update_handler: Farmbot.Host.UpdateHandler, nerves_hub_handler: Farmbot.Host.NervesHubHandler,
# firmware_handler: Farmbot.Firmware.UartHandler # firmware_handler: Farmbot.Firmware.UartHandler
] ]
config :nerves_runtime,
enable_syslog: false,
target: "host",
kernel: [
autoload_modules: false
]
config :farmbot, :uart_handler, tty: "/dev/ttyACM0" config :farmbot, :uart_handler, tty: "/dev/ttyACM0"

View File

@ -27,7 +27,8 @@ config :farmbot, :farmware, first_part_farmware_manifest_url: nil
config :farmbot, :behaviour, config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization, authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Test.SystemTasks, system_tasks: Farmbot.Test.SystemTasks,
update_handler: FarmbotTestSupport.TestUpdateHandler update_handler: FarmbotTestSupport.TestUpdateHandler,
nerves_hub_handler: Farmbot.Host.NervesHubHandler
config :farmbot, Farmbot.Repo, [ config :farmbot, Farmbot.Repo, [
adapter: Sqlite.Ecto2, adapter: Sqlite.Ecto2,

View File

@ -0,0 +1,9 @@
use Mix.Config
config :nerves_hub,
client: Farmbot.System.NervesHubClient,
public_keys: [File.read!("priv/staging.pub"), File.read!("priv/prod.pub")]
config :nerves_hub, NervesHub.Socket, [
reconnect_interval: 5_000,
]

View File

@ -33,6 +33,9 @@ config :farmbot, :init, [
# Allows for first boot configuration. # Allows for first boot configuration.
Farmbot.Target.Bootstrap.Configurator, Farmbot.Target.Bootstrap.Configurator,
# Handles OTA updates from NervesHub
Farmbot.System.NervesHubClient,
# Start up Network # Start up Network
Farmbot.Target.Network, Farmbot.Target.Network,
@ -76,9 +79,9 @@ config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization, authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Target.SystemTasks, system_tasks: Farmbot.Target.SystemTasks,
firmware_handler: Farmbot.Firmware.StubHandler, firmware_handler: Farmbot.Firmware.StubHandler,
update_handler: Farmbot.Target.UpdateHandler,
pin_binding_handler: Farmbot.Target.PinBinding.AleHandler, pin_binding_handler: Farmbot.Target.PinBinding.AleHandler,
leds_handler: Farmbot.Target.Leds.AleHandler leds_handler: Farmbot.Target.Leds.AleHandler,
nerves_hub_handler: Farmbot.System.NervesHubClient
local_file = Path.join(System.user_home!(), ".ssh/id_rsa.pub") local_file = Path.join(System.user_home!(), ".ssh/id_rsa.pub")
local_key = if File.exists?(local_file), do: [File.read!(local_file)], else: [] local_key = if File.exists?(local_file), do: [File.read!(local_file)], else: []
@ -86,6 +89,14 @@ local_key = if File.exists?(local_file), do: [File.read!(local_file)], else: []
config :nerves_network, regulatory_domain: "US" config :nerves_network, regulatory_domain: "US"
config :nerves_firmware_ssh, authorized_keys: local_key config :nerves_firmware_ssh, authorized_keys: local_key
config :nerves_init_gadget,
ifname: "usb0",
address_method: :dhcpd,
mdns_domain: "farmbot.local",
node_name: nil,
node_host: :mdns_domain
config :shoehorn, config :shoehorn,
init: [:nerves_runtime, :nerves_init_gadget, :nerves_firmware_ssh], init: [:nerves_runtime, :nerves_init_gadget, :nerves_firmware_ssh],
handler: Farmbot.ShoehornHandler,
app: :farmbot app: :farmbot

View File

@ -17,7 +17,7 @@ config :farmbot, Farmbot.System.ConfigStorage,
database: "/root/config-#{Mix.env()}.sqlite3" database: "/root/config-#{Mix.env()}.sqlite3"
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage] config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
config :logger, LoggerBackendSqlite, [ config :logger, LoggerBackendSqlite, [
database: "/root/debug_logs.sqlite3", database: "/root/debug_logs.sqlite3",
max_logs: 10000 max_logs: 10000
@ -33,6 +33,9 @@ config :farmbot, :init, [
# Allows for first boot configuration. # Allows for first boot configuration.
Farmbot.Target.Bootstrap.Configurator, Farmbot.Target.Bootstrap.Configurator,
# Handles OTA updates from NervesHub
Farmbot.System.NervesHubClient,
# Start up Network # Start up Network
Farmbot.Target.Network, Farmbot.Target.Network,
@ -75,9 +78,9 @@ config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization, authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Target.SystemTasks, system_tasks: Farmbot.Target.SystemTasks,
firmware_handler: Farmbot.Firmware.StubHandler, firmware_handler: Farmbot.Firmware.StubHandler,
update_handler: Farmbot.Target.UpdateHandler,
pin_binding_handler: Farmbot.Target.PinBinding.AleHandler, pin_binding_handler: Farmbot.Target.PinBinding.AleHandler,
leds_handler: Farmbot.Target.Leds.AleHandler leds_handler: Farmbot.Target.Leds.AleHandler,
nerves_hub_handler: Farmbot.System.NervesHubClient
config :nerves_network, regulatory_domain: "US" config :nerves_network, regulatory_domain: "US"
config :shoehorn, config :shoehorn,

161
docs/RELEASE.md 100644
View File

@ -0,0 +1,161 @@
## Provisioning the Release System
Publishing a FarmBotOS release requires coordination of a few different systems.
* FarmBot Web App
* FarmBot OS
* NervesHub
* CircleCI
* GitHub branches and releases
## Legacy System
The legacy system is somewhat simpiler. It goes as follows:
### Pull request into `master` branch.
```
git checkout master
git merge staging
git push origin master
```
Obviously this will not actually work because of testing and things, but that
is what happens behind the scenes on GitHub.
### CircleCI builds release
Once merged into master CircleCI will create a `draft` release on GitHub. This
must be QA'd and confirmed manually before publishing. Once published, FarmBot
will check the `OS_AUTO_UPDATE_URL` in the JWT.
### Beta updates
Users may opt into beta updates by settings `os_beta_updates: true` on their
device's `FbosConfig` endpoint.
The system works the same as production, except that the release is drafted based
on the `beta` branch. The other change is that CircleCI publishes a _real_ release
overwriting a previous release of this version if it exists. the release is tagged
as `pre_release: true` in GitHub releases. this prevents the production system
from downloading `beta` updates.
## NervesHub System
The NervesHub system is simpiler to use, but more complex to setup.
### User registration
Create a admin user. This should be the same `ADMIN_EMAIL` used in
the WebApp configuration.
```
mix nerves_hub.user register
Email address: admin@farmbot.io
Name: farmbot
NervesHub password: *super secret*
Local password: *super duper secret*
```
```
mix nerves_hub.product create
name: farmbot
Local password: *super duper secret*
```
### Signing keys
Now a choice will need to be made.
If fwup signing keys existed beforehand (they did for FarmBot Inc) do:
```
mix nerves_hub.key import <PATH/TO/PUBLIC/KEY> <PATH/TO/PRIVATE/KEY>
Local password: *super duper secret*
```
If new keys are required (probably named "prod") do:
```
mix nerves_hub.key create <NAME>
Local password: *super duper secret*
```
### Exporting certs and keys
The API and CI need copies of these keys and certs.
```
mix nerves_hub.user cert export
Local password: *super duper secret*
User certs exported to: <PATH/TO/EXPORTED_CERTS.tar.gz>
tar -xf <PATH/TO/EXPORTED_CERTS.tar.gz> -C nerves-hub/
```
```
mix nerves_hub.key export prod
Local password: *super duper secret*
Fwup keys exported to: <PATH/TO/EXPORTED_KEYS.tar.gz>
tar -xf <PATH/TO/EXPORTED_KEYS.tar.gz> -C nerves-hub/
```
You will also need the CA cert bundle for the WebApp:
(this may only work for BASH)
```bash
{ curl -s https://raw.githubusercontent.com/nerves-hub/nerves_hub_cli/master/priv/ca_certs/root-ca.pem | head -20 \
&& curl -s https://raw.githubusercontent.com/nerves-hub/nerves_hub_cli/master/priv/ca_certs/intermediate-server-ca.pem | head -20 \
&& curl -s https://raw.githubusercontent.com/nerves-hub/nerves_hub_cli/master/priv/ca_certs/intermediate-user-ca.pem | head -20;
} > nerves-hub/nerves-hub-ca-certs.pem
```
Now the FarmBot API needs the values of in it's environment:
* `NERVES_HUB_KEY` -> `cat nerves-hub/key.pem`
* `NERVES_HUB_CERT` -> `cat nerves-hub/cert.pem`
* `NERVES_HUB_CA` -> `cat nerves-hub/nerves-hub-ca-certs.pem`
CircleCI will need:
* `NERVES_HUB_KEY` -> `cat nerves-hub/key.pem`
* `NERVES_HUB_CERT` -> `cat nerves-hub/cert.pem`
* `NERVES_HUB_FW_PRIVATE_KEY` -> `cat nerves-hub/<KEY NAME>.priv`
* `NERVES_HUB_FW_PUBLIC_KEY` -> `cat nerves-hub/<KEY NAME>.pub`
### Provisioning and Tags
Tags/Deployments follow this structure:
```json
[
"application:<MIX_ENV>",
"channel:<CHANNEL>"
]
```
NOTE: the tags **NOT** json objects, they are simple strings
split by a `:` character. This is done _only_ for readability.
where `MIX_ENV` will be one of:
* `dev`
* `prod`
and `CHANNEL` will be one of:
* `beta`
* `stable`
There should be at least one deployment matching the following
tags:
* `["application:dev", "channel:stable"]`
* a development FBOS release on the `stable` channel
* `["application:prod", "channel:stable"]`
* a production FBOS release on the `stable` channel
* `["application:dev", "channel:beta"]`
* a development FBOS release on the `beta` channel
* `["application:prod", "channel:beta"]`
* a production FBOS release on the `beta` channel
* `["application:dev", "channel:stable"]`
* a development FBOS release on the `stable` channel
* `["application:prod", "channel:stable"]`
* a production FBOS release on the `stable` channel
* `["application:dev", "channel:beta"]`
* a development FBOS release on the `beta` channel
* `["application:prod", "channel:beta"]`
* a production FBOS release on the `beta` channel
### First time setup
```
heroku config:set NERVES_HUB_CERT="$NERVES_HUB_CERT" --app=$HEROKU_APPNAME
heroku config:set NERVES_HUB_KEY="$NERVES_HUB_KEY" --app=$HEROKU_APPNAME
heroku config:set NERVES_HUB_CA="$NERVES_HUB_CA" --app=$HEROKU_APPNAME
heroku config:set NERVES_HUB_ORG="$NERVES_HUB_ORG" --app=$HEROKU_APPNAME
```

View File

@ -3,7 +3,6 @@ defmodule Farmbot do
Supervises the individual modules that make up the Farmbot Application. Supervises the individual modules that make up the Farmbot Application.
This is the entry point of the application. This is the entry point of the application.
""" """
require Farmbot.Logger require Farmbot.Logger
require Logger require Logger
use Application use Application
@ -20,6 +19,7 @@ defmodule Farmbot do
def init([]) do def init([]) do
children = [ children = [
{Farmbot.System.Registry, []},
{Farmbot.Logger.Supervisor, []}, {Farmbot.Logger.Supervisor, []},
{Farmbot.System.Supervisor, []}, {Farmbot.System.Supervisor, []},
{Farmbot.Bootstrap.Supervisor, []} {Farmbot.Bootstrap.Supervisor, []}

View File

@ -52,6 +52,10 @@ defmodule Farmbot.BotState do
end end
end end
def set_update_available(bool) when is_boolean(bool) do
GenStage.call(__MODULE__, {:set_update_available, bool})
end
def report_disk_usage(percent) when is_number(percent) do def report_disk_usage(percent) when is_number(percent) do
GenStage.call(__MODULE__, {:report_disk_usage, percent}) GenStage.call(__MODULE__, {:report_disk_usage, percent})
end end
@ -201,6 +205,12 @@ defmodule Farmbot.BotState do
{:noreply, [], state} {:noreply, [], state}
end end
def handle_call({:set_update_available, bool}, _from, state) do
new_info_settings = %{state.informational_settings | update_available: bool}
state = %{state | informational_settings: new_info_settings}
{:reply, :ok, [state], state}
end
def handle_call({:report_disk_usage, percent}, _from, state) do def handle_call({:report_disk_usage, percent}, _from, state) do
new_info_settings = %{state.informational_settings | disk_usage: percent} new_info_settings = %{state.informational_settings | disk_usage: percent}
state = %{state | informational_settings: new_info_settings} state = %{state | informational_settings: new_info_settings}
@ -391,6 +401,7 @@ defmodule Farmbot.BotState do
defstruct [ defstruct [
informational_settings: %{ informational_settings: %{
update_available: false,
controller_version: @version, controller_version: @version,
firmware_version: "disconnected", firmware_version: "disconnected",
firmware_commit: @arduino_commit, firmware_commit: @arduino_commit,
@ -405,7 +416,7 @@ defmodule Farmbot.BotState do
locked: false, locked: false,
cache_bust: 0, cache_bust: 0,
soc_temp: 0, # degrees celcius soc_temp: 0, # degrees celcius
wifi_level: nil, # decibels wifi_level: nil, # decibels
uptime: 0, # seconds uptime: 0, # seconds
memory_usage: 0, # megabytes memory_usage: 0, # megabytes
disk_usage: 0, # percent disk_usage: 0, # percent

View File

@ -145,7 +145,9 @@ defmodule Farmbot.BotState.Transport.AMQP do
{:noreply, [], state} {:noreply, [], state}
end end
def handle_info({:basic_deliver, payload, %{routing_key: key}}, state) do def handle_info({:basic_deliver, payload, %{routing_key: key} = options}, state) do
# TODO(Connor) - investigate acking
# :ok = Basic.ack(state.chan, options[:delivery_tag])
if GenServer.whereis(Farmbot.Repo) do if GenServer.whereis(Farmbot.Repo) do
device = state.bot device = state.bot
route = String.split(key, ".") route = String.split(key, ".")
@ -165,6 +167,8 @@ defmodule Farmbot.BotState.Transport.AMQP do
["bot", ^device, "logs"] -> {:noreply, [], state} ["bot", ^device, "logs"] -> {:noreply, [], state}
["bot", ^device, "status"] -> {:noreply, [], state} ["bot", ^device, "status"] -> {:noreply, [], state}
["bot", ^device, "from_device"] -> {:noreply, [], state} ["bot", ^device, "from_device"] -> {:noreply, [], state}
["bot", ^device, "nerves_hub"] ->
handle_nerves_hub(payload, options, state)
_ -> _ ->
Logger.warn 3, "got unknown routing key: #{key}" Logger.warn 3, "got unknown routing key: #{key}"
{:noreply, [], state} {:noreply, [], state}
@ -268,6 +272,19 @@ defmodule Farmbot.BotState.Transport.AMQP do
end end
end end
def handle_nerves_hub(payload, options, state) do
:ok = Basic.ack(state.chan, options[:delivery_tag])
case Poison.decode(payload) do
{:ok, data} ->
cert = data["cert"] |> Base.decode64!()
key = data["key"] |> Base.decode64!()
:ok = Farmbot.System.NervesHub.configure_certs(cert, key)
:ok = Farmbot.System.NervesHub.connect()
{:noreply, [], state}
_ -> {:noreply, [], state}
end
end
defp push_bot_log(chan, bot, log) do defp push_bot_log(chan, bot, log) do
json = Poison.encode!(log) json = Poison.encode!(log)
:ok = AMQP.Basic.publish chan, @exchange, "bot.#{bot}.logs", json :ok = AMQP.Basic.publish chan, @exchange, "bot.#{bot}.logs", json
@ -306,6 +323,7 @@ defmodule Farmbot.BotState.Transport.AMQP do
q_base <- device, q_base <- device,
:ok <- Basic.qos(chan, [global: true]), :ok <- Basic.qos(chan, [global: true]),
{:ok, _} <- AMQP.Queue.declare(chan, q_base <> "_from_clients", [auto_delete: true]), {:ok, _} <- AMQP.Queue.declare(chan, q_base <> "_from_clients", [auto_delete: true]),
from_clients <- [routing_key: "bot.#{device}.from_clients"], from_clients <- [routing_key: "bot.#{device}.from_clients"],
{:ok, _} <- AMQP.Queue.purge(chan, q_base <> "_from_clients"), {:ok, _} <- AMQP.Queue.purge(chan, q_base <> "_from_clients"),
@ -315,8 +333,14 @@ defmodule Farmbot.BotState.Transport.AMQP do
sync <- [routing_key: "bot.#{device}.sync.#"], sync <- [routing_key: "bot.#{device}.sync.#"],
:ok <- AMQP.Queue.bind(chan, q_base <> "_auto_sync", @exchange, sync), :ok <- AMQP.Queue.bind(chan, q_base <> "_auto_sync", @exchange, sync),
{:ok, _} <- AMQP.Queue.declare(chan, q_base <> "_nerves_hub", [auto_delete: false, durable: true]),
nerves_hub <- [routing_key: "bot.#{device}.nerves_hub"],
:ok <- AMQP.Queue.bind(chan, q_base <> "_nerves_hub", @exchange, nerves_hub),
{:ok, _tag} <- Basic.consume(chan, q_base <> "_from_clients", self(), [no_ack: true]), {:ok, _tag} <- Basic.consume(chan, q_base <> "_from_clients", self(), [no_ack: true]),
{:ok, _tag} <- Basic.consume(chan, q_base <> "_auto_sync", self(), [no_ack: true]) do {:ok, _tag} <- Basic.consume(chan, q_base <> "_auto_sync", self(), [no_ack: true]),
{:ok, _tag} <- Basic.consume(chan, q_base <> "_nerves_hub", self(), [])
do
%State{conn: conn, chan: chan, bot: device} %State{conn: conn, chan: chan, bot: device}
end end
end end

View File

@ -14,6 +14,8 @@ defmodule Farmbot.CeleryScript.AST.Node.ChangeOwnership do
email = pair_map["email"] email = pair_map["email"]
secret = pair_map["secret"] |> Base.decode64!(padding: false, ignore: :whitespace) secret = pair_map["secret"] |> Base.decode64!(padding: false, ignore: :whitespace)
server = pair_map["server"] || get_config_value(:string, "authorization", "server") server = pair_map["server"] || get_config_value(:string, "authorization", "server")
:ok = Farmbot.System.NervesHub.deconfigure()
case test_credentials(email, secret, server) do case test_credentials(email, secret, server) do
{:ok, _token} -> {:ok, _token} ->
Logger.warn 1, "Farmbot is changing ownership to #{email} - #{server}." Logger.warn 1, "Farmbot is changing ownership to #{email} - #{server}."

View File

@ -5,15 +5,7 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
def execute(%{package: :farmbot_os}, _, env) do def execute(%{package: :farmbot_os}, _, env) do
env = mutate_env(env) env = mutate_env(env)
case Farmbot.System.Updates.check_updates() do nerves_hub_updater(env)
{:error, reason} -> {:error, reason, env}
nil -> {:ok, env}
{%Version{} = version, url} ->
case Farmbot.System.Updates.download_and_apply_update({version, url}) do
:ok -> {:ok, env}
{:error, reason} -> {:error, reason, env}
end
end
end end
def execute(%{package: :arduino_firmware}, _, env) do def execute(%{package: :arduino_firmware}, _, env) do
@ -25,4 +17,11 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
env = mutate_env(env) env = mutate_env(env)
Farmbot.CeleryScript.AST.Node.UpdateFarmware.execute(args, [], env) Farmbot.CeleryScript.AST.Node.UpdateFarmware.execute(args, [], env)
end end
defp nerves_hub_updater(env) do
case Farmbot.System.NervesHub.check_update() do
nil -> {:ok, env}
url when is_binary(url) -> {:ok, env}
end
end
end end

View File

@ -4,6 +4,7 @@ defmodule Farmbot.Project do
@version Mix.Project.config[:version] @version Mix.Project.config[:version]
@target Mix.Project.config[:target] @target Mix.Project.config[:target]
@commit Mix.Project.config[:commit] @commit Mix.Project.config[:commit]
@branch Mix.Project.config[:branch]
@arduino_commit Mix.Project.config[:arduino_commit] @arduino_commit Mix.Project.config[:arduino_commit]
@env Mix.env() @env Mix.env()
@ -15,6 +16,10 @@ defmodule Farmbot.Project do
@compile {:inline, commit: 0} @compile {:inline, commit: 0}
def commit, do: @commit def commit, do: @commit
@doc "*#{@branch}*"
@compile {:inline, branch: 0}
def branch, do: @branch
@doc "*#{@arduino_commit}*" @doc "*#{@arduino_commit}*"
@compile {:inline, arduino_commit: 0} @compile {:inline, arduino_commit: 0}
def arduino_commit, do: @arduino_commit def arduino_commit, do: @arduino_commit

View File

@ -11,6 +11,7 @@ defmodule Farmbot.System.Init.FSCheckup do
@version Farmbot.Project.version() @version Farmbot.Project.version()
@target Farmbot.Project.target() @target Farmbot.Project.target()
@env Farmbot.Project.env() @env Farmbot.Project.env()
System.put_env("NERVES_FW_VCS_IDENTIFIER", @ref)
@doc false @doc false
def start_link(_, opts \\ []) do def start_link(_, opts \\ []) do
@ -68,7 +69,7 @@ defmodule Farmbot.System.Init.FSCheckup do
try do try do
Elixir.Logger.add_backend(LoggerBackendSqlite) Elixir.Logger.add_backend(LoggerBackendSqlite)
catch catch
:exit, r -> :exit, r ->
Logger.error 1, "Could not start disk logging: #{inspect r}" Logger.error 1, "Could not start disk logging: #{inspect r}"
Elixir.Logger.remove_backend(LoggerBackendSqlite) Elixir.Logger.remove_backend(LoggerBackendSqlite)
File.rm(Path.join([@data_path, "root", "debug_logs.sqlite3"])) File.rm(Path.join([@data_path, "root", "debug_logs.sqlite3"]))

View File

@ -0,0 +1,29 @@
defmodule Farmbot.System.Init.Suprevisor do
@moduledoc false
use Supervisor
import Farmbot.System.Init
def start_link(args \\ []) do
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
end
def init([]) do
init_mods =
Application.get_env(:farmbot, :init)
|> Enum.map(fn child -> fb_init(child, [[], [name: child]]) end)
children = [
# Load kernel modules
worker(Farmbot.System.Init.KernelMods, [[], []]),
# Ensure filesystem
worker(Farmbot.System.Init.FSCheckup, [[], []]),
# Ensure ecto + migrations
supervisor(Farmbot.System.Init.Ecto, [[], []]),
# Ensure config_storage
supervisor(Farmbot.System.ConfigStorage, []),
worker(Farmbot.System.ConfigStorage.Dispatcher, []),
] ++ init_mods
Supervisor.init(children, [strategy: :one_for_one])
end
end

View File

@ -0,0 +1,142 @@
defmodule Farmbot.System.NervesHub do
@moduledoc """
Wrapper for NervesHub that can support both host and target environments.
Some things can be configured via Mix.Config:
config :farmbot, Farmbot.System.NervesHub, [
app_env: "application:some_other_tag",
extra_tags: ["some", "more", "tags"]
]
## On Target Devices
FarmBotOS requires some weird behaviour. `:nerves_hub` should not be started
with the rest of the application. Connecting should input serial, cert and key
into NervesRuntimeKV, restart NervesRuntimeKV, then _finally_ start `:nerves_hub`.
## On host.
Just return :ok to everything.
"""
@handler Application.get_env(:farmbot, :behaviour)[:nerves_hub_handler]
|| Mix.raise("missing :nerves_hub_handler module")
@doc "Function to return a String serial number. "
@callback serial_number() :: String.t()
@doc "Connect to NervesHub."
@callback connect() :: :ok | :error
@doc "Burn the serial number into persistent storage."
@callback provision(serial :: String.t) :: :ok | :error
@doc "Burn the cert and key into persistent storage."
@callback configure_certs(cert :: String.t(), key :: String.t()) :: :ok | :error
@doc "Return the current confuration including serial, cert and key."
@callback config() :: [String.t() | nil]
@doc "Remove serial, cert, and key from persistent storage."
@callback deconfigure() :: :ok | :error
@doc "Should return a url to an update or nil."
@callback check_update() :: String.t() | nil
use GenServer
use Farmbot.Logger
def start_link(args \\ []) do
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end
def init([]) do
send self(), :configure
{:ok, :not_configured}
end
def terminate(reason, state) do
Logger.warn 1, "Nerves Hub crash: #{inspect reason} when in state: #{inspect state}"
end
def handle_info(:configure, :not_configured) do
channel = case Farmbot.Project.branch() do
"master" -> "channel:stable"
"beta" -> "channel:beta"
"staging" -> "channel:staging"
branch -> "channel:#{branch}"
end
if Process.whereis(Farmbot.HTTP) do
app_config = Application.get_env(:farmbot, __MODULE__, [])
"application:" <> _ = app_env = app_config[:app_env] || "application:#{Farmbot.Project.env()}"
extra_tags = app_config[:extra_tags] || []
if nil in get_config() do
Logger.info 1, "doing initial nerves hub configuration."
:ok = deconfigure()
:ok = provision()
:ok = configure([app_env, channel] ++ extra_tags)
else
connect()
end
{:noreply, :configured}
else
Logger.debug 3, "Server not configured yet. Waiting 10_000 ms to try again."
Process.send_after(self(), :configure, 10_000)
{:noreply, :not_configured}
end
end
def get_config do
@handler.config()
end
def connect do
Logger.debug 1, "Connecting to NervesHub"
@handler.connect()
end
# Returns the current serial number.
def serial do
@handler.serial_number()
end
# Sets Serial number in environment.
def provision do
Logger.debug 1, "Provisioning NervesHub"
:ok = @handler.provision(serial())
end
# Creates a device in NervesHub
# or updates it if one exists.
def configure(tags) when is_list(tags) do
Logger.debug 1, "Configuring NervesHub: #{inspect tags}"
payload = %{
serial_number: serial(),
tags: tags
} |> Farmbot.JSON.encode!()
_ = Farmbot.HTTP.post!("/api/device_cert", payload)
:ok
end
# Message comes over AMQP.
def configure_certs("-----BEGIN CERTIFICATE-----" <> _ = cert,
"-----BEGIN EC PRIVATE KEY-----" <> _ = key) do
Logger.debug 1, "Configuring certs for NervesHub."
:ok = @handler.configure_certs(cert, key)
:ok
end
def deconfigure do
Logger.debug 1, "Deconfiguring NervesHub"
:ok = @handler.deconfigure()
:ok
end
def check_update do
Logger.debug 1, "Check update NervesHub"
@handler.check_update()
end
end

View File

@ -1,10 +1,11 @@
defmodule Farmbot.System.Registry do defmodule Farmbot.System.Registry do
@moduledoc "Farmbot System Global Registry" @moduledoc "Farmbot System Global Registry"
@reg FarmbotRegistry @reg FarmbotRegistry
use GenServer
@doc false @doc false
def start_link do def start_link(args) do
GenServer.start_link(__MODULE__, [], [name: __MODULE__]) GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end end
@doc "Dispatch a global event from a namespace." @doc "Dispatch a global event from a namespace."

View File

@ -3,7 +3,6 @@ defmodule Farmbot.System.Supervisor do
Supervises Platform specific stuff for Farmbot to operate Supervises Platform specific stuff for Farmbot to operate
""" """
use Supervisor use Supervisor
import Farmbot.System.Init
@doc false @doc false
def start_link(args) do def start_link(args) do
@ -11,25 +10,11 @@ defmodule Farmbot.System.Supervisor do
end end
def init([]) do def init([]) do
before_init_children = [ children = [
worker(Farmbot.System.Registry, []), supervisor(Farmbot.System.Init.Suprevisor, []),
worker(Farmbot.System.Init.KernelMods, [[], []]), worker(Farmbot.System.NervesHub, []),
worker(Farmbot.System.Init.FSCheckup, [[], []]),
supervisor(Farmbot.System.Init.Ecto, [[], []]),
supervisor(Farmbot.System.ConfigStorage, []),
worker(Farmbot.System.ConfigStorage.Dispatcher, []),
]
init_mods =
Application.get_env(:farmbot, :init)
|> Enum.map(fn child -> fb_init(child, [[], [name: child]]) end)
after_init_children = [
supervisor(Farmbot.System.Updates, []),
worker(Farmbot.EasterEggs, []), worker(Farmbot.EasterEggs, []),
] ]
Supervisor.init(children, strategy: :one_for_one)
all_children = before_init_children ++ init_mods ++ after_init_children
Supervisor.init(all_children, strategy: :one_for_all)
end end
end end

View File

@ -68,7 +68,7 @@ defmodule Farmbot.System do
_, _ -> _, _ ->
Logger.error 1, "Firmware unavailable. Can't emergency_lock" Logger.error 1, "Firmware unavailable. Can't emergency_lock"
end end
end end
defp do_reset(reason) do defp do_reset(reason) do
@ -77,6 +77,7 @@ defmodule Farmbot.System do
nil -> reboot("Escape factory reset: #{inspect reason}") nil -> reboot("Escape factory reset: #{inspect reason}")
{:ignore, reason} -> reboot(reason) {:ignore, reason} -> reboot(reason)
_ -> _ ->
Farmbot.System.NervesHub.deconfigure()
path = Farmbot.Farmware.Installer.install_root_path() path = Farmbot.Farmware.Installer.install_root_path()
File.rm_rf(path) File.rm_rf(path)
Ecto.drop() Ecto.drop()

View File

@ -1,18 +0,0 @@
defmodule Farmbot.System.UpdateHandler do
@moduledoc "Behaviour for setting up OTA updates."
@doc "Called before and update."
@callback before_update :: :ok | {:error, term}
@doc "Called after a reboot."
@callback post_update :: :ok | {:error, term}
@doc "Apply a fw update."
@callback apply_firmware(Path.t) :: :ok | {:error, term}
@doc "Setup updates."
@callback setup(atom) :: :ok | {:error, term}
@doc "If a fw has already been applied."
@callback requires_reboot? :: boolean
end

View File

@ -1,59 +0,0 @@
defmodule Farmbot.System.UpdateTimer do
@moduledoc false
@twelve_hours 4.32e+7 |> round()
use GenServer
use Farmbot.Logger
def wait_for_http(callback) do
case Process.whereis(Farmbot.HTTP) do
nil ->
Process.sleep(1000)
wait_for_http(callback)
pid when is_pid(pid) ->
do_check()
Process.send_after(callback, :checkup, @twelve_hours)
end
end
def start_link do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def terminate(reason, _) do
Logger.error 1, "Failed to check updates: #{inspect reason}"
end
def init([]) do
spawn __MODULE__, :wait_for_http, [self()]
Farmbot.System.Registry.subscribe(self())
{:ok, []}
end
def handle_info(:checkup, state) do
do_check()
Process.send_after(self(), :checkup, @twelve_hours)
{:noreply, state}
end
def handle_info({Farmbot.System.Registry, {:config_storage, {"settings", "beta_opt_in", true}}}, state) do
if Process.whereis(Farmbot.Bootstrap.AuthTask) do
Logger.debug 3, "Opted into beta updates. Refreshing token."
Farmbot.Bootstrap.AuthTask.force_refresh()
end
{:noreply, state}
end
def handle_info({Farmbot.System.Registry, _info}, state) do
{:noreply, state}
end
defp do_check do
osau = Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
case Farmbot.System.Updates.check_updates() do
{:error, err} -> Logger.error 1, "Error checking for updates: #{inspect err}"
nil -> Logger.debug 3, "No updates available as of #{inspect Timex.now()}"
url -> if osau, do: Farmbot.System.Updates.download_and_apply_update(url)
end
end
end

View File

@ -1,326 +0,0 @@
defmodule Farmbot.System.Updates do
@moduledoc "Handles over the air updates."
use Supervisor
use Farmbot.Logger
alias Farmbot.System.ConfigStorage
import ConfigStorage, only: [get_config_value: 3, update_config_value: 4]
@data_path Application.get_env(:farmbot, :data_path)
@target Farmbot.Project.target()
@current_version Farmbot.Project.version()
@env Farmbot.Project.env()
@update_handler Application.get_env(:farmbot, :behaviour)[:update_handler]
@update_handler || Mix.raise("Please configure update_handler")
@doc "Overwrite os update server field"
def override_update_server(url) do
update_config_value(:string, "settings", "os_update_server_overwrite", url)
end
defmodule Release do
@moduledoc false
defmodule Asset do
@moduledoc false
defstruct [:name, :browser_download_url]
end
defstruct [
tag_name: nil,
target_commitish: nil,
name: nil,
draft: false,
prerelease: true,
body: nil,
assets: []
]
end
defmodule CurrentStuff do
@moduledoc false
import Farmbot.Project
defstruct [
:token,
:beta_opt_in,
:os_update_server_overwrite,
:currently_on_beta,
:env,
:commit,
:target,
:version
]
@doc "Get the current stuff. Fields can be replaced for testing."
def get(replace \\ %{}) do
os_update_server_overwrite = get_config_value(:string, "settings", "os_update_server_overwrite")
beta_opt_in? = is_binary(os_update_server_overwrite) || get_config_value(:bool, "settings", "beta_opt_in")
token_bin = get_config_value(:string, "authorization", "token")
currently_on_beta? = get_config_value(:bool, "settings", "currently_on_beta")
token = if token_bin, do: Farmbot.Jwt.decode!(token_bin), else: nil
opts = %{
token: token,
beta_opt_in: beta_opt_in?,
currently_on_beta: currently_on_beta?,
os_update_server_overwrite: os_update_server_overwrite,
env: env(),
commit: commit(),
target: target(),
version: version()
} |> Map.merge(Map.new(replace))
struct(__MODULE__, opts)
end
end
@doc "Downloads and applies an update file."
def download_and_apply_update({%Version{} = version, dl_url}) do
if @update_handler.requires_reboot?() do
Logger.warn 1, "Can't apply update. An update is already staged. Please reboot and try again."
{:error, :reboot_required}
else
fe_constant = "FBOS_OTA"
dl_fun = Farmbot.BotState.download_progress_fun(fe_constant)
# TODO(Connor): I'd like this to have a version number..
dl_path = Path.join(@data_path, "ota.fw")
results = http_adapter().download_file(dl_url, dl_path, dl_fun, "", [])
Farmbot.BotState.clear_progress_fun(fe_constant)
case results do
{:ok, path} -> apply_firmware("beta" in (version.pre || []), path, true)
{:error, reason} -> {:error, reason}
end
end
end
@doc """
Force check for updates.
Does _NOT_ download or apply update.
"""
def check_updates(release \\ nil, current_stuff \\ nil)
# All the HTTP Requests happen here.
def check_updates(nil, current_stuff) do
# Get current values.
current_stuff_mut = %{
token: token,
beta_opt_in: beta_opt_in,
os_update_server_overwrite: server_override,
env: env,
} = current_stuff || CurrentStuff.get()
cond do
# Don't allow non producion envs to check production env updates.
env != :prod -> {:error, :wrong_env}
# Don't check if the token is nil.
is_nil(token) -> {:error, :no_token}
# Allows the server to be overwrote.
is_binary(server_override) ->
Logger.debug 3, "Update server override: #{server_override}"
get_release_from_url(server_override)
# Beta updates should check twice.
beta_opt_in ->
Logger.debug 3, "Checking for beta updates."
token
|> Map.get(:beta_os_update_server)
|> get_release_from_url()
# Conditions exhausted. We _must_ be on a production release.
true ->
Logger.debug 3, "Checking for production updates."
token
|> Map.get(:os_update_server)
|> get_release_from_url()
end
|> case do
# Beta needs to make two requests:
# check for a later beta update, if no later beta update,
# Check for a later production release.
%Release{} = release when beta_opt_in ->
do_check_production_release = fn() ->
token
|> Map.get(:os_update_server)
|> get_release_from_url()
|> case do
%Release{} = prod_release -> check_updates(prod_release, current_stuff_mut)
err -> err
end
end
check_updates(release, current_stuff_mut) || do_check_production_release.()
# Production release; no beta. Check the release for an asset.
%Release{} = release -> check_updates(release, current_stuff_mut)
err -> err
end
end
# Check against the release struct. Not HTTP requests from here out.
def check_updates(%Release{} = rel, %CurrentStuff{} = current_stuff) do
%{
beta_opt_in: beta_opt_in,
currently_on_beta: currently_on_beta,
commit: current_commit,
version: current_version,
} = current_stuff
release_version = String.trim(rel.tag_name, "v") |> Version.parse!()
is_beta_release? = "beta" in (release_version.pre || [])
version_comp = Version.compare(current_version, release_version)
release_commit = rel.target_commitish
commits_equal? = current_commit == release_commit
prerelease = rel.prerelease
cond do
# Don't bother if the release is a draft. Not sure how/if this can happen.
rel.draft ->
Logger.warn 1, "Not checking draft release."
nil
# Only check prerelease if
# current_version is less than or equal to release_version
# AND
# the commits are not equal.
prerelease and is_beta_release? and beta_opt_in and !commits_equal? ->
# beta release get marked as greater than non beta release, so we need
# to manually check the versions by removing the pre part.
case Version.compare(current_version, %{release_version | pre: []}) do
:lt ->
Logger.debug 3, "Current version (#{current_version}) is less than beta release (#{release_version})"
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
:eq ->
if currently_on_beta do
Logger.debug 3, "Current version (#{current_version}) (beta updates enabled) is equal to beta release (#{release_version})"
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
else
Logger.debug 3, "Current version (#{current_version}) (beta updates disabled) is equal to latest beta (#{release_version})"
nil
end
:gt ->
Logger.debug 3, "Current version (#{current_version}) is greater than latest beta release (#{release_version})"
nil
end
# if the current version is less than the release version.
!prerelease and version_comp == :lt ->
Logger.debug 3, "Current version is less than release."
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
# If the version isn't different, but the commits are different,
# This happens for beta releases.
!prerelease and version_comp == :eq and !commits_equal? ->
Logger.debug 3, "Current version is equal to release, but commits are not equal."
try_find_dl_url_in_asset(rel.assets, release_version, current_stuff)
# Conditions exhausted. No updates available.
true ->
comparison_str = "version check: current version: #{current_version} #{version_comp} latest release version: #{release_version} \n"<>
"commit check: current commit: #{current_commit} latest release commit: #{release_commit}: (equal: #{commits_equal?})"
Logger.debug 3, "No updates available: \ntarget: #{Farmbot.Project.target()}: \nprerelease: #{prerelease} \n#{comparison_str}"
nil
end
end
@doc "Finds a asset url if it exists, nil if not."
@spec try_find_dl_url_in_asset([%Release.Asset{}], Version.t, %CurrentStuff{}) :: {Version.t, String.t}
def try_find_dl_url_in_asset(assets, version, current_stuff)
def try_find_dl_url_in_asset([%Release.Asset{name: name, browser_download_url: bdurl} | rest], %Version{} = release_version_obj, current_stuff) do
release_version = to_string(release_version_obj)
current_target = to_string(current_stuff.target)
expected_name = "farmbot-#{current_target}-#{release_version}.fw"
if match?(^expected_name, name) do
{release_version_obj, bdurl}
else
Logger.debug 3, "Incorrect asset name for target: #{current_target}: #{name}"
try_find_dl_url_in_asset(rest, release_version_obj, current_stuff)
end
end
def try_find_dl_url_in_asset([], release_version, current_stuff) do
Logger.warn 2, "No update in assets for #{current_stuff.target()} for #{release_version}"
nil
end
@doc "HTTP request to fetch a Release."
def get_release_from_url(url) when is_binary(url) do
Logger.debug 3, "Checking for updates: #{url}"
case http_adapter().get(url) do
# This can happen on beta updates, if on an old token
# and a new beta release was published.
{:ok, %{status_code: 404}} ->
Logger.warn 1, "Got a 404 checking for updates: #{url}. Fetching a new token. Try that again"
Farmbot.Bootstrap.AuthTask.force_refresh()
{:error, :token_refresh}
# Decode the HTTP body as a release.
{:ok, %{status_code: 200, body: body}} ->
pattern = struct(Release, [assets: [struct(Release.Asset)]])
case Poison.decode(body, as: pattern) do
{:ok, %Release{} = rel} -> rel
_err -> {:error, :bad_release_body}
end
# Error situations
{:ok, %{status_code: _code, body: body}} -> {:error, body}
err -> err
end
end
@doc "Apply an OS (fwup) firmware."
def apply_firmware(is_beta?, file_path, reboot) when is_boolean(is_beta?) do
Logger.busy 1, "Applying #{@target} OS update (beta=#{is_beta?})"
before_update()
case @update_handler.apply_firmware(file_path) do
:ok ->
update_config_value(:bool, "settings", "currently_on_beta", is_beta?)
Logger.success 1, "OS Firmware updated!"
if reboot do
Logger.warn 1, "Farmbot going down for OS update."
Farmbot.System.reboot("OS Firmware Update.")
end
{:error, reason} ->
Logger.error 1, "Failed to apply update: #{inspect reason}"
{:error, reason}
end
end
# Private
defp maybe_post_update do
case File.read(update_file()) do
{:ok, @current_version} -> :ok
{:ok, old_version} ->
Logger.info 1, "Updating from #{old_version} to #{@current_version}"
@update_handler.post_update()
{:error, :enoent} ->
Logger.info 1, "Updating to #{@current_version}"
{:error, err} -> raise err
end
before_update()
end
defp before_update, do: File.write!(update_file(), @current_version)
defp update_file, do: Path.join(@data_path, "update")
defp http_adapter, do: Farmbot.HTTP
@doc false
def start_link do
Supervisor.start_link(__MODULE__, [], [name: __MODULE__])
end
@doc false
def init([]) do
case @update_handler.setup(@env) do
:ok ->
maybe_post_update()
children = [
worker(Farmbot.System.UpdateTimer, [])
]
opts = [strategy: :one_for_one]
supervise(children, opts)
{:error, reason} -> {:stop, reason}
end
end
end

26
mix.exs
View File

@ -2,10 +2,13 @@ defmodule Farmbot.Mixfile do
use Mix.Project use Mix.Project
@target System.get_env("MIX_TARGET") || "host" @target System.get_env("MIX_TARGET") || "host"
@version Path.join(__DIR__, "VERSION") |> File.read!() |> String.trim() @version Path.join(__DIR__, "VERSION") |> File.read!() |> String.trim()
@commit System.cmd("git", ~w"rev-parse --verify HEAD") |> elem(0) |> String.trim()
@branch System.cmd("git", ~w"rev-parse --abbrev-ref HEAD") |> elem(0) |> String.trim()
System.put_env("NERVES_FW_VCS_IDENTIFIER", @commit)
defp commit do defp commit, do: @commit
System.cmd("git", ~w"rev-parse --verify HEAD") |> elem(0) |> String.trim()
end defp branch, do: @branch
defp arduino_commit do defp arduino_commit do
opts = [cd: "c_src/farmbot-arduino-firmware"] opts = [cd: "c_src/farmbot-arduino-firmware"]
@ -28,6 +31,7 @@ defmodule Farmbot.Mixfile do
version: @version, version: @version,
target: @target, target: @target,
commit: commit(), commit: commit(),
branch: branch(),
arduino_commit: arduino_commit(), arduino_commit: arduino_commit(),
archives: [nerves_bootstrap: "~> 1.2"], archives: [nerves_bootstrap: "~> 1.2"],
build_embedded: Mix.env() == :prod, build_embedded: Mix.env() == :prod,
@ -58,13 +62,23 @@ defmodule Farmbot.Mixfile do
] ]
end end
def application do def application("host") do
[ [
mod: {Farmbot, []}, mod: {Farmbot, []},
extra_applications: [:logger, :eex, :ssl, :inets, :runtime_tools] extra_applications: [:logger, :eex, :ssl, :inets, :runtime_tools]
] ]
end end
def application(_target) do
[
mod: {Farmbot, []},
extra_applications: [:logger, :eex, :ssl, :inets, :runtime_tools],
included_applications: [:nerves_hub]
]
end
def application(), do: application(@target)
defp docs do defp docs do
[ [
main: "building", main: "building",
@ -122,7 +136,8 @@ defmodule Farmbot.Mixfile do
{:ring_logger, "~> 0.5"}, {:ring_logger, "~> 0.5"},
{:bbmustache, "~> 1.6"}, {:bbmustache, "~> 1.6"},
{:sqlite_ecto2, "~> 2.2"}, {:sqlite_ecto2, "~> 2.2"},
{:logger_backend_sqlite, "~> 2.1"} {:logger_backend_sqlite, "~> 2.1"},
{:nerves_hub_cli, "~> 0.4"}
] ]
end end
@ -141,6 +156,7 @@ defmodule Farmbot.Mixfile do
system(target) ++ system(target) ++
[ [
{:nerves_runtime, "~> 0.8"}, {:nerves_runtime, "~> 0.8"},
{:nerves_hub, github: "nerves-hub/nerves_hub", override: true},
{:nerves_firmware, "~> 0.4"}, {:nerves_firmware, "~> 0.4"},
{:nerves_firmware_ssh, "~> 0.3"}, {:nerves_firmware_ssh, "~> 0.3"},
{:nerves_init_gadget, "~> 0.5", only: :dev}, {:nerves_init_gadget, "~> 0.5", only: :dev},

View File

@ -32,8 +32,7 @@
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"inch_ex": {:hex, :inch_ex, "1.0.1", "1f0af1a83cec8e56f6fc91738a09c838e858db3d78ef5f2ec040fe4d5a62dabf", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"}, "jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
@ -48,12 +47,15 @@
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"nerves": {:hex, :nerves, "1.3.1", "4e2002315b38d38c6bfb5d3e33d7f4a32057037c3f2026af64d0867bc1741b21", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "nerves": {:hex, :nerves, "1.3.4", "9523cc1936f173c99cf15a132c2b24f9c6f1a5cfe3327bbcd518ff7e441327d3", [:mix], [{:distillery, "2.0.10", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.4.0", "d5efcc49179fff8f3cd4542831820082d6abf2290fb5251c85e54ea51614b35d", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"},
"nerves_runtime": {:git, "https://github.com/nerves-project/nerves_runtime.git", "ea804f9326c649681e3fcf72e8dd81d26a6508b6", []},
"nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, "net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbcs": {:hex, :pbcs, "0.1.0", "6f79ce81d93edf5ac41fcd8b32fb203ad6895ebdb33d115e14a5bd955b90020a", [:mix], [], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
@ -70,8 +72,11 @@
"sqlite_ecto2": {:hex, :sqlite_ecto2, "2.2.5", "f111a48188b0640effb7f2952071c4cf285501d3ce090820a7c2fc20af3867e9", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "2.2.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm"}, "sqlite_ecto2": {:hex, :sqlite_ecto2, "2.2.5", "f111a48188b0640effb7f2952071c4cf285501d3ce090820a7c2fc20af3867e9", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "2.2.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm"},
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, "sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
"x509": {:hex, :x509, "0.3.0", "c6f3db66960c6e4f424d1e6cca5c7d730e0a577af8dc115a613f4560ce1df6d3", [:mix], [], "hexpm"},
} }

View File

@ -23,6 +23,7 @@
"esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"}, "esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"},
"farmbot_system_rpi0": {:hex, :farmbot_system_rpi0, "1.5.1-farmbot.0", "61458e70b5e48dfe9774a0389e4ec4c7935a2f4e4488e94a708dc66f3b904e8c", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.3", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"}, "farmbot_system_rpi0": {:hex, :farmbot_system_rpi0, "1.5.1-farmbot.0", "61458e70b5e48dfe9774a0389e4ec4c7935a2f4e4488e94a708dc66f3b904e8c", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.3", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.1.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"},
"fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"},
"fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
@ -45,6 +46,8 @@
"nerves": {:hex, :nerves, "1.3.1", "4e2002315b38d38c6bfb5d3e33d7f4a32057037c3f2026af64d0867bc1741b21", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "nerves": {:hex, :nerves, "1.3.1", "4e2002315b38d38c6bfb5d3e33d7f4a32057037c3f2026af64d0867bc1741b21", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"}, "nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.3", "79c42303ddbfd89ae6f5b4b19a4397a6188df21ca0e7a6573c2399e081fb7d25", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.3", "79c42303ddbfd89ae6f5b4b19a4397a6188df21ca0e7a6573c2399e081fb7d25", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub": {:git, "https://github.com/nerves-hub/nerves_hub.git", "2da9a6fea2b53fa9e0251045b06e265e21847d24", []},
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.4.0", "d5efcc49179fff8f3cd4542831820082d6abf2290fb5251c85e54ea51614b35d", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.1", "07f3eeb9acb3f919b3b34b36f552bb38d70f2d29ace63f3f23f33eee6a1ca693", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.1", "07f3eeb9acb3f919b3b34b36f552bb38d70f2d29ace63f3f23f33eee6a1ca693", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"},
"nerves_network": {:hex, :nerves_network, "0.3.7", "200767191b1ded5a61cddbacd3efdce92442cc055bdc37c20ca8c7cb1d964098", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_network": {:hex, :nerves_network, "0.3.7", "200767191b1ded5a61cddbacd3efdce92442cc055bdc37c20ca8c7cb1d964098", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"},
@ -62,6 +65,8 @@
"net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, "net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
"one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbcs": {:hex, :pbcs, "0.1.0", "6f79ce81d93edf5ac41fcd8b32fb203ad6895ebdb33d115e14a5bd955b90020a", [:mix], [], "hexpm"},
"phoenix_channel_client": {:hex, :phoenix_channel_client, "0.3.2", "188f6e4cad20da03e04f685416a86f682c413efca7c72303f479b94662cb897c", [:mix], [{:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
@ -79,8 +84,11 @@
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, "sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"}, "system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
"x509": {:hex, :x509, "0.3.0", "c6f3db66960c6e4f424d1e6cca5c7d730e0a577af8dc115a613f4560ce1df6d3", [:mix], [], "hexpm"},
} }

View File

@ -23,13 +23,14 @@
"esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"}, "esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"},
"farmbot_system_rpi3": {:hex, :farmbot_system_rpi3, "1.5.1-farmbot.0", "6bc583c00beb764fa917a9071a709dfc0e2c296376fcad8c35eb281f5186657f", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.3", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"}, "farmbot_system_rpi3": {:hex, :farmbot_system_rpi3, "1.5.1-farmbot.0", "6bc583c00beb764fa917a9071a709dfc0e2c296376fcad8c35eb281f5186657f", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.5.3", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
"fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"}, "fs": {:hex, :fs, "3.4.0", "6d18575c250b415b3cad559e6f97a4c822516c7bc2c10bfbb2493a8f230f5132", [:rebar3], [], "hexpm"},
"fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"}, "gen_stage": {:hex, :gen_stage, "0.14.0", "65ae78509f85b59d360690ce3378d5096c3130a0694bab95b0c4ae66f3008fad", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"}, "jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
@ -42,9 +43,11 @@
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves": {:hex, :nerves, "1.3.1", "4e2002315b38d38c6bfb5d3e33d7f4a32057037c3f2026af64d0867bc1741b21", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "nerves": {:hex, :nerves, "1.3.4", "9523cc1936f173c99cf15a132c2b24f9c6f1a5cfe3327bbcd518ff7e441327d3", [:mix], [{:distillery, "2.0.10", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"}, "nerves_firmware": {:hex, :nerves_firmware, "0.4.0", "ac2fed915a7ca4bb69f567d9b742d77cffc3a6a56420ce65e870c8c34119b935", [:mix], [], "hexpm"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.3", "79c42303ddbfd89ae6f5b4b19a4397a6188df21ca0e7a6573c2399e081fb7d25", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.3", "79c42303ddbfd89ae6f5b4b19a4397a6188df21ca0e7a6573c2399e081fb7d25", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub": {:git, "https://github.com/nerves-hub/nerves_hub.git", "2da9a6fea2b53fa9e0251045b06e265e21847d24", []},
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.4.0", "d5efcc49179fff8f3cd4542831820082d6abf2290fb5251c85e54ea51614b35d", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.1", "07f3eeb9acb3f919b3b34b36f552bb38d70f2d29ace63f3f23f33eee6a1ca693", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.1", "07f3eeb9acb3f919b3b34b36f552bb38d70f2d29ace63f3f23f33eee6a1ca693", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"},
"nerves_network": {:hex, :nerves_network, "0.3.7", "200767191b1ded5a61cddbacd3efdce92442cc055bdc37c20ca8c7cb1d964098", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_network": {:hex, :nerves_network, "0.3.7", "200767191b1ded5a61cddbacd3efdce92442cc055bdc37c20ca8c7cb1d964098", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"},
@ -61,6 +64,8 @@
"net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, "net_logger": {:hex, :net_logger, "0.1.0", "59be302c09cf70dab164810c923ccb9a976eda7270e5a32b93ba8aeb850de1d6", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
"one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbcs": {:hex, :pbcs, "0.1.0", "6f79ce81d93edf5ac41fcd8b32fb203ad6895ebdb33d115e14a5bd955b90020a", [:mix], [], "hexpm"},
"phoenix_channel_client": {:hex, :phoenix_channel_client, "0.3.2", "188f6e4cad20da03e04f685416a86f682c413efca7c72303f479b94662cb897c", [:mix], [{:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.6.4", "35618dd2cc009b69b000f785452f6b370f76d099ece199733fea27bc473f809d", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
@ -78,8 +83,11 @@
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, "sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"}, "system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
"x509": {:hex, :x509, "0.3.0", "c6f3db66960c6e4f424d1e6cca5c7d730e0a577af8dc115a613f4560ce1df6d3", [:mix], [], "hexpm"},
} }

View File

@ -1,7 +0,0 @@
defmodule Nerves.Firmware do
@moduledoc false
@doc false
def upgrade_and_finalize(_), do: :ok
def reboot, do: :ok
end

View File

@ -0,0 +1,27 @@
defmodule Farmbot.Host.NervesHubHandler do
@behaviour Farmbot.System.NervesHub
def serial_number do
{:ok, [_ | [{_ifname, info} | _]]} = :inet.getifaddrs()
:io_lib.format('~2.16.0B~2.16.0B~2.16.0B~2.16.0B~2.16.0B~2.16.0B', info[:hwaddr])
|> to_string()
|> String.trim()
end
def connect, do: :ok
def provision(_serial), do: :ok
def configure_certs(_cert, _key), do: :ok
def deconfigure, do: :ok
def config, do: [
serial_number(),
serial_number(),
"Not a real cert",
"Not a real key"
]
def check_update, do: nil
end

View File

@ -1,26 +0,0 @@
defmodule Farmbot.Host.UpdateHandler do
@moduledoc false
@behaviour Farmbot.System.UpdateHandler
use Farmbot.Logger
# Update Handler callbacks
def apply_firmware(_file_path) do
:ok
end
def before_update do
:ok
end
def post_update do
:ok
end
def setup(_env) do
:ok
end
def requires_reboot?, do: false
end

View File

@ -0,0 +1,119 @@
defmodule Farmbot.System.NervesHubClient do
@moduledoc """
Client that decides when an update should be done.
"""
use GenServer
require Logger
@behaviour NervesHub.Client
@behaviour Farmbot.System.NervesHub
import Farmbot.System.ConfigStorage, only: [get_config_value: 3]
def serial_number("rpi0"), do: serial_number("rpi")
def serial_number("rpi3"), do: serial_number("rpi")
def serial_number(plat) do
:os.cmd('/usr/bin/boardid -b uboot_env -u nerves_serial_number -b uboot_env -u serial_number -b #{plat}')
|> to_string()
|> String.trim()
end
def serial_number, do: serial_number(Farmbot.Project.target())
def connect do
Logger.info "Starting NervesHub app."
# NervesHub replaces it's own env on startup. Reset it.
Application.put_env(:nerves_hub, NervesHub.Socket, [reconnect_interval: 5000])
# Stop Nerves Hub if it is running.
_ = Application.stop(:nerves_hub)
# Cause NervesRuntime.KV to restart.
_ = GenServer.stop(Nerves.Runtime.KV)
{:ok, _} = Application.ensure_all_started(:nerves_hub)
# Wait for a few seconds for good luck.
Process.sleep(1000)
r = NervesHub.connect()
Logger.info "NervesHub started: #{inspect(r, limit: :infinity)}"
:ok
end
def provision(serial) do
Nerves.Runtime.KV.UBootEnv.put("nerves_serial_number", serial)
Nerves.Runtime.KV.UBootEnv.put("nerves_fw_serial_number", serial)
end
def configure_certs(cert, key) do
Nerves.Runtime.KV.UBootEnv.put("nerves_hub_cert", cert)
Nerves.Runtime.KV.UBootEnv.put("nerves_hub_key", key)
:ok
end
def deconfigure() do
Nerves.Runtime.KV.UBootEnv.put("nerves_hub_cert", "")
Nerves.Runtime.KV.UBootEnv.put("nerves_hub_key", "")
Nerves.Runtime.KV.UBootEnv.put("nerves_serial_number", "")
Nerves.Runtime.KV.UBootEnv.put("nerves_fw_serial_number", "")
:ok
end
def config() do
[
Nerves.Runtime.KV.get("nerves_fw_serial_number"),
Nerves.Runtime.KV.get("nerves_hub_cert"),
Nerves.Runtime.KV.get("nerves_hub_key"),
]
end
def check_update do
case GenServer.call(__MODULE__, :check_update) do
# If updates were disabled, and an update is queued
{:ignore, _url} -> NervesHub.update()
_ -> nil
end
end
# Callback for NervesHub.Client
def update_available(args) do
GenServer.call(__MODULE__, {:update_available, args}, :infinity)
end
def handle_fwup_message({:progress, percent}) do
Logger.info("FWUP Stream Progress: #{percent}%")
alias Farmbot.BotState.JobProgress
prog = %JobProgress.Percent{percent: percent}
if Process.whereis(Farmbot.BotState) do
Farmbot.BotState.set_job_progress("FBOS_OTA", prog)
end
:ok
end
def handle_fwup_message({:error, _, reason}) do
Logger.error "FWUP Error: #{reason}"
:ok
end
def handle_fwup_message(_) do
:ok
end
def start_link(_, _) do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
{:ok, nil}
end
def handle_call({:update_available, %{"firmware_url" => url}}, _, _state) do
if Process.whereis(Farmbot.BotState) do
Farmbot.BotState.set_update_available(true)
end
case get_config_value(:bool, "settings", "os_auto_update") do
true -> {:reply, :apply, {:apply, url}}
false -> {:reply, :ignore, {:ignore, url}}
end
end
def handle_call(:check_update, _from, state) do
{:reply, state, state}
end
end

View File

@ -28,7 +28,6 @@ defmodule Farmbot.Target.Uevent do
end end
def handle_info({:system_registry, :global, new_reg}, %{} = old_reg) do def handle_info({:system_registry, :global, new_reg}, %{} = old_reg) do
require IEx; IEx.pry
new_ttys = get_in(new_reg, [:state, "subsystems", "tty"]) || [] new_ttys = get_in(new_reg, [:state, "subsystems", "tty"]) || []
old_ttys = get_in(old_reg, [:state, "subsystems", "tty"]) || [] old_ttys = get_in(old_reg, [:state, "subsystems", "tty"]) || []
case new_ttys -- old_ttys do case new_ttys -- old_ttys do

View File

@ -1,66 +0,0 @@
defmodule Farmbot.Target.UpdateHandler do
@moduledoc "Handles prep and post OTA update."
@behaviour Farmbot.System.UpdateHandler
use Farmbot.Logger
# Update Handler callbacks
def apply_firmware(fw_file_path) do
{meta_bin, 0} = System.cmd("fwup", ~w"-i #{fw_file_path} -m")
meta_bin
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.split(&1, "="))
|> Map.new(fn([key, val]) ->
{key, val |> String.trim_leading("\"") |> String.trim_trailing("\"")}
end)
|> log_meta()
Nerves.Firmware.upgrade_and_finalize(fw_file_path)
end
defp log_meta(meta_map) do
target = "target: #{meta_map["meta-platform"]}"
product = "product: #{meta_map["meta-product"]}"
version = "version: #{meta_map["meta-version"]}"
create_time = "created: #{meta_map["meta-creation-date"]}"
msg = """
Applying Firmware:
#{create_time}
#{target}
#{product}
#{version}
"""
Logger.debug 1, msg
end
def before_update, do: :ok
def post_update do
alias Farmbot.Firmware.UartHandler.Update
hw = Farmbot.System.ConfigStorage.get_config_value(:string, "settings", "firmware_hardware")
is_beta? = Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "currently_on_beta")
if is_beta? do
Logger.debug 1, "Forcing beta image arduino firmware flash."
Update.force_update_firmware(hw)
else
Update.maybe_update_firmware(hw)
end
:ok
end
def setup(:prod) do
file = "#{:code.priv_dir(:farmbot)}/fwup-key.pub"
Application.put_env(:nerves_firmware, :pub_key_path, file)
if File.exists?(file), do: :ok, else: {:error, :no_pub_file}
end
def setup(_) do
:ok
end
def requires_reboot? do
!Nerves.Firmware.allow_upgrade?
end
end

File diff suppressed because it is too large Load Diff

2658
priv/farmduino.hex 100644

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
priv/prod.pub 100644
View File

@ -0,0 +1 @@
ZL9J9sLK3GWa7YezB3v41Kxl0TmE15CARjApOGcNa1c=

1
priv/staging.pub 100644
View File

@ -0,0 +1 @@
qIbhsGnUVHGEu++RAss2U0caXySSwDBVKkK35sO0vVU=

View File

@ -1,4 +1,6 @@
-setcookie democookie -setcookie democookie
+Bc
-kernel shell_history enabled
-mode embedded -mode embedded
-sname farmbot -sname farmbot
-noshell -noshell

View File

@ -1,7 +1,6 @@
#!/bin/bash #!/bin/bash
TARGETS="rpi0 \ TARGETS="rpi0 \
rpi3 \ rpi3 \
bbb \
host host
" "
for target in $TARGETS; do for target in $TARGETS; do

View File

@ -1,19 +0,0 @@
defmodule Farmbot.System.UpdateTimerTest do
use ExUnit.Case, async: false
alias Farmbot.System.ConfigStorage
test "Opting into beta updates should refresh token" do
Farmbot.System.Registry.subscribe(self())
old = ConfigStorage.get_config_value(:string, "authorization", "token")
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", false)
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", true)
assert_receive {Farmbot.System.Registry, {:config_storage, {"settings", "beta_opt_in", true}}}
assert_receive {Farmbot.System.Registry, {:authorization, :new_token}}, 1000
new = ConfigStorage.get_config_value(:string, "authorization", "token")
assert old != new
end
end

View File

@ -1,108 +0,0 @@
defmodule Farmbot.System.UpdatesTest do
use ExUnit.Case, async: false
alias Farmbot.System.Updates
alias Farmbot.System.Updates.{Release, CurrentStuff}
@old_version Farmbot.Project.version |> Version.parse! |> Map.update(:major, nil, &Kernel.-(&1, 1)) |> to_string()
@new_version Farmbot.Project.version |> Version.parse! |> Map.update(:major, nil, &Kernel.+(&1, 1)) |> to_string()
@commit Farmbot.Project.commit
@version Farmbot.Project.version()
@os_update_server "http://fake_os_update_server.com"
@beta_os_update_server "http://beta_os_update_server.com"
@fake_asset_url "http://fake_release_asset.com"
describe "CurrentStuff replacement" do
test "replaces valid things in the current stuff struct" do
r = CurrentStuff.get(env: :almost_prod)
assert r.env == :almost_prod
end
test "Allows and igrnores arbitry data" do
r = CurrentStuff.get(some_key: :some_val)
refute Map.get(r, :some_key)
end
end
@tag :external
test "checks for updates for prod rpi3 no beta combo" do
# updating from old to new version should work
current = CurrentStuff.get(os_update_server_overwrite: nil, beta_opt_in: false, env: :prod, target: :rpi3, version: @old_version)
assert Updates.check_updates(nil, current)
end
@tag :external
test "checks for updates for prod rpi3 with beta combo" do
# old version to later beta
current = CurrentStuff.get(os_update_server_overwrite: nil, env: :prod, target: :rpi3, version: @old_version, beta_opt_in: true)
assert Updates.check_updates(nil, current)
end
test "no token gives error" do
current = CurrentStuff.get(token: nil)
assert match?({:error, _}, Updates.check_updates(nil, current))
end
test "dev env should not update to prod" do
current = CurrentStuff.get(env: :dev)
assert match?({:error, _}, Updates.check_updates(nil, current))
end
test "updates of the same version should not return a url" do
current = CurrentStuff.get(current_stub())
release = release_stub()
refute Updates.check_updates(release, current)
end
test "Draft releases" do
current = CurrentStuff.get(current_stub())
release = %{release_stub() | draft: true}
refute Updates.check_updates(release, current)
end
test "Opting into beta won't downgrade from a prod release to a previous beta" do
current = CurrentStuff.get(%{current_stub() | beta_opt_in: true, version: @new_version})
release = release_stub()
refute Updates.check_updates(release, current)
end
test "Normal upgrade path: current is less than latest" do
current = CurrentStuff.get(%{current_stub() | version: @old_version})
release = release_stub()
assert match?({%Version{}, @fake_asset_url}, Updates.check_updates(release, current))
end
test "versions equal, but commits not equal" do
current = CurrentStuff.get(%{current_stub() | commit: String.reverse(@commit)})
release = release_stub()
assert match?({%Version{}, @fake_asset_url}, Updates.check_updates(release, current))
end
defp current_stub do
%{
token: %Farmbot.Jwt{
os_update_server: @os_update_server,
beta_os_update_server: @beta_os_update_server,
},
beta_opt_in: false,
currently_on_beta: false,
os_update_server_overwrite: nil,
env: :prod,
commit: @commit,
target: :rpi3,
version: @version
}
end
defp release_stub do
%Release{
tag_name: "v#{@version}",
target_commitish: @commit,
name: "Stub Release",
draft: false,
prerelease: false,
body: "This is a stub!",
assets: [%Release.Asset{name: "farmbot-rpi3-#{@version}.fw", browser_download_url: @fake_asset_url}]
}
end
end

View File

@ -1,26 +0,0 @@
defmodule FarmbotTestSupport.TestUpdateHandler do
@moduledoc "Handles prep and post OTA update."
@behaviour Farmbot.System.UpdateHandler
use Farmbot.Logger
# Update Handler callbacks
def apply_firmware(_file_path) do
:ok
end
def before_update do
:ok
end
def post_update do
:ok
end
def setup(_env) do
:ok
end
def requires_reboot?, do: false
end