Nerves Hub (#647)
parent
15ad5dc4ab
commit
a23d223995
|
@ -1,7 +1,7 @@
|
|||
version: 2.0
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: nervesproject/nerves_system_br:1.4.2
|
||||
- image: nervesproject/nerves_system_br:1.5.2
|
||||
|
||||
install_elixir: &install_elixir
|
||||
run:
|
||||
|
@ -38,136 +38,182 @@ install_slack_helpers: &install_slack_helpers
|
|||
command: |
|
||||
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:
|
||||
test:
|
||||
<<: *defaults
|
||||
environment:
|
||||
MIX_ENV: test
|
||||
MIX_TARGET: host
|
||||
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
|
||||
ELIXIR_VERSION: 1.7.3
|
||||
SKIP_ARDUINO_BUILD: 1
|
||||
steps:
|
||||
- checkout
|
||||
- run: git submodule update --init --recursive
|
||||
- restore_cache:
|
||||
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
|
||||
- <<: *install_elixir
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
|
||||
- <<: *install_arduino
|
||||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v6-dep-cache-{{ checksum "mix.lock.host" }}
|
||||
- <<: *fetch_and_compile_deps
|
||||
- run:
|
||||
name: Test Farmbot OS
|
||||
command: |
|
||||
mix deps.get
|
||||
mix compile
|
||||
- save_cache:
|
||||
key: v6-dep-cache-{{ checksum "mix.lock.host" }}
|
||||
key: v7-fbos-host-test-dependency-cache-{{ checksum "mix.lock.host" }}
|
||||
paths:
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- deps/host
|
||||
- save_cache:
|
||||
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:
|
||||
build_rpi3_prod_firmware:
|
||||
<<: *defaults
|
||||
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
|
||||
ENV: CI
|
||||
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
|
||||
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" }}
|
||||
- <<: *install_elixir
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
|
||||
- <<: *install_arduino
|
||||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix deps.get
|
||||
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix compile
|
||||
- run: MIX_ENV=prod MIX_TARGET=rpi3 mix firmware
|
||||
- run:
|
||||
name: Build Farmbot OS Firmware
|
||||
command: |
|
||||
mix deps.get
|
||||
mix compile --force
|
||||
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:
|
||||
key: v6-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
key: v7-fbos-rpi3-prod-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
paths:
|
||||
- _build/rpi3
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- _build/rpi3/
|
||||
- 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: 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
|
||||
- ~/.nerves/
|
||||
- store_artifacts:
|
||||
path: /nerves/deploy/system/artifacts
|
||||
destination: images
|
||||
- save_cache:
|
||||
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: nerves/deploy/system-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
|
||||
paths:
|
||||
- ./artifacts
|
||||
|
||||
deploy_beta_firmware:
|
||||
- "/nerves/deploy/system"
|
||||
deploy_rpi3_prod_firmware_master:
|
||||
<<: *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
|
||||
- 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_jq
|
||||
- run:
|
||||
name: Run setup script
|
||||
command: bash .circleci/setup-heroku.sh
|
||||
|
@ -175,18 +221,16 @@ jobs:
|
|||
fingerprints:
|
||||
- "97:92:32:5d:d7:96:e1:fa:f3:6b:f3:bd:d6:aa:84:c6"
|
||||
- run:
|
||||
name: Install dependencies
|
||||
name: Sign Firmware
|
||||
command: |
|
||||
wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip
|
||||
unzip ghr_v0.5.4_linux_amd64.zip
|
||||
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
|
||||
chmod +x ./jq-linux64
|
||||
echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
|
||||
mv /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw
|
||||
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
|
||||
- run:
|
||||
command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES
|
||||
- restore_cache:
|
||||
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- 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:
|
||||
name: Update heroku env
|
||||
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-staging
|
||||
elixir slack_message.ex $SLACK_MESSAGE
|
||||
|
||||
firmware_prod:
|
||||
publish_rpi3_prod_firmware_master_release:
|
||||
<<: *defaults
|
||||
environment:
|
||||
MIX_TARGET: rpi3
|
||||
MIX_ENV: prod
|
||||
ENV: CI
|
||||
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
|
||||
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
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v6-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
|
||||
- <<: *install_arduino
|
||||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- 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
|
||||
- <<: *install_ghr
|
||||
- <<: *install_slack_helpers
|
||||
- run:
|
||||
name: Decode fwup priv key
|
||||
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
|
||||
name: Run setup script
|
||||
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:
|
||||
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
|
||||
name: Sign Firmware
|
||||
command: |
|
||||
wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip
|
||||
unzip ghr_v0.5.4_linux_amd64.zip
|
||||
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
|
||||
chmod +x ./jq-linux64
|
||||
echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
|
||||
mv /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat VERSION).fw /tmp/farmbot-${MIX_TARGET}-$(cat VERSION).fw
|
||||
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
|
||||
- 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:
|
||||
command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES
|
||||
- restore_cache:
|
||||
key: v6-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- 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:
|
||||
version: 2
|
||||
test_firmware_upload:
|
||||
test:
|
||||
jobs:
|
||||
- test:
|
||||
context: org-global
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
# Merging is blocked on these branches until tests pass.
|
||||
- beta
|
||||
- master
|
||||
- firmware_dev:
|
||||
context: org-global
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- beta
|
||||
- master
|
||||
|
||||
deploy_beta:
|
||||
- staging
|
||||
deploy_stable_production:
|
||||
jobs:
|
||||
- firmware_beta:
|
||||
context: org-global
|
||||
- build_rpi3_prod_firmware:
|
||||
context: farmbot-production
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- beta
|
||||
- deploy_beta_firmware:
|
||||
context: org-global
|
||||
- master
|
||||
- deploy_rpi3_prod_firmware_master:
|
||||
context: farmbot-production
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- beta
|
||||
- master
|
||||
requires:
|
||||
- firmware_beta
|
||||
|
||||
deploy_prod:
|
||||
- build_rpi3_prod_firmware
|
||||
- publish_rpi3_prod_firmware_master_release:
|
||||
context: org-global
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
requires:
|
||||
- build_rpi3_prod_firmware
|
||||
deploy_stable_staging:
|
||||
jobs:
|
||||
- firmware_prod:
|
||||
context: org-global
|
||||
- build_rpi3_prod_firmware:
|
||||
context: farmbot-staging
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- deploy_prod_firmware:
|
||||
context: org-global
|
||||
- deploy_rpi3_prod_firmware_master:
|
||||
context: farmbot-staging
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
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
|
|
@ -57,4 +57,4 @@ RELEASE_NOTES
|
|||
|
||||
.elixir_ls
|
||||
nerves-hub
|
||||
*.pem
|
||||
*.pem
|
||||
|
|
|
@ -69,7 +69,11 @@ config :farmbot,
|
|||
|
||||
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
|
||||
"host" ->
|
||||
|
@ -82,5 +86,7 @@ case target do
|
|||
do: import_config("target/#{target}.exs")
|
||||
|
||||
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
|
||||
|
|
|
@ -45,8 +45,15 @@ config :farmbot, default_server: "https://staging.farm.bot"
|
|||
config :farmbot, :behaviour, [
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Host.SystemTasks,
|
||||
update_handler: Farmbot.Host.UpdateHandler,
|
||||
nerves_hub_handler: Farmbot.Host.NervesHubHandler,
|
||||
# firmware_handler: Farmbot.Firmware.UartHandler
|
||||
]
|
||||
|
||||
config :nerves_runtime,
|
||||
enable_syslog: false,
|
||||
target: "host",
|
||||
kernel: [
|
||||
autoload_modules: false
|
||||
]
|
||||
|
||||
config :farmbot, :uart_handler, tty: "/dev/ttyACM0"
|
||||
|
|
|
@ -27,7 +27,8 @@ config :farmbot, :farmware, first_part_farmware_manifest_url: nil
|
|||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Test.SystemTasks,
|
||||
update_handler: FarmbotTestSupport.TestUpdateHandler
|
||||
update_handler: FarmbotTestSupport.TestUpdateHandler,
|
||||
nerves_hub_handler: Farmbot.Host.NervesHubHandler
|
||||
|
||||
config :farmbot, Farmbot.Repo, [
|
||||
adapter: Sqlite.Ecto2,
|
||||
|
|
|
@ -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,
|
||||
]
|
|
@ -33,6 +33,9 @@ config :farmbot, :init, [
|
|||
# Allows for first boot configuration.
|
||||
Farmbot.Target.Bootstrap.Configurator,
|
||||
|
||||
# Handles OTA updates from NervesHub
|
||||
Farmbot.System.NervesHubClient,
|
||||
|
||||
# Start up Network
|
||||
Farmbot.Target.Network,
|
||||
|
||||
|
@ -76,9 +79,9 @@ config :farmbot, :behaviour,
|
|||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Target.SystemTasks,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
update_handler: Farmbot.Target.UpdateHandler,
|
||||
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_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_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,
|
||||
init: [:nerves_runtime, :nerves_init_gadget, :nerves_firmware_ssh],
|
||||
handler: Farmbot.ShoehornHandler,
|
||||
app: :farmbot
|
||||
|
|
|
@ -17,7 +17,7 @@ config :farmbot, Farmbot.System.ConfigStorage,
|
|||
database: "/root/config-#{Mix.env()}.sqlite3"
|
||||
|
||||
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
|
||||
|
||||
|
||||
config :logger, LoggerBackendSqlite, [
|
||||
database: "/root/debug_logs.sqlite3",
|
||||
max_logs: 10000
|
||||
|
@ -33,6 +33,9 @@ config :farmbot, :init, [
|
|||
# Allows for first boot configuration.
|
||||
Farmbot.Target.Bootstrap.Configurator,
|
||||
|
||||
# Handles OTA updates from NervesHub
|
||||
Farmbot.System.NervesHubClient,
|
||||
|
||||
# Start up Network
|
||||
Farmbot.Target.Network,
|
||||
|
||||
|
@ -75,9 +78,9 @@ config :farmbot, :behaviour,
|
|||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Target.SystemTasks,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
update_handler: Farmbot.Target.UpdateHandler,
|
||||
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 :shoehorn,
|
||||
|
|
|
@ -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
|
||||
```
|
|
@ -3,7 +3,6 @@ defmodule Farmbot do
|
|||
Supervises the individual modules that make up the Farmbot Application.
|
||||
This is the entry point of the application.
|
||||
"""
|
||||
|
||||
require Farmbot.Logger
|
||||
require Logger
|
||||
use Application
|
||||
|
@ -20,6 +19,7 @@ defmodule Farmbot do
|
|||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.System.Registry, []},
|
||||
{Farmbot.Logger.Supervisor, []},
|
||||
{Farmbot.System.Supervisor, []},
|
||||
{Farmbot.Bootstrap.Supervisor, []}
|
||||
|
|
|
@ -52,6 +52,10 @@ defmodule Farmbot.BotState do
|
|||
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
|
||||
GenStage.call(__MODULE__, {:report_disk_usage, percent})
|
||||
end
|
||||
|
@ -201,6 +205,12 @@ defmodule Farmbot.BotState do
|
|||
{:noreply, [], state}
|
||||
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
|
||||
new_info_settings = %{state.informational_settings | disk_usage: percent}
|
||||
state = %{state | informational_settings: new_info_settings}
|
||||
|
@ -391,6 +401,7 @@ defmodule Farmbot.BotState do
|
|||
|
||||
defstruct [
|
||||
informational_settings: %{
|
||||
update_available: false,
|
||||
controller_version: @version,
|
||||
firmware_version: "disconnected",
|
||||
firmware_commit: @arduino_commit,
|
||||
|
@ -405,7 +416,7 @@ defmodule Farmbot.BotState do
|
|||
locked: false,
|
||||
cache_bust: 0,
|
||||
soc_temp: 0, # degrees celcius
|
||||
wifi_level: nil, # decibels
|
||||
wifi_level: nil, # decibels
|
||||
uptime: 0, # seconds
|
||||
memory_usage: 0, # megabytes
|
||||
disk_usage: 0, # percent
|
||||
|
|
|
@ -145,7 +145,9 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
{:noreply, [], state}
|
||||
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
|
||||
device = state.bot
|
||||
route = String.split(key, ".")
|
||||
|
@ -165,6 +167,8 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
["bot", ^device, "logs"] -> {:noreply, [], state}
|
||||
["bot", ^device, "status"] -> {: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}"
|
||||
{:noreply, [], state}
|
||||
|
@ -268,6 +272,19 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
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
|
||||
json = Poison.encode!(log)
|
||||
:ok = AMQP.Basic.publish chan, @exchange, "bot.#{bot}.logs", json
|
||||
|
@ -306,6 +323,7 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
q_base <- device,
|
||||
|
||||
:ok <- Basic.qos(chan, [global: true]),
|
||||
|
||||
{:ok, _} <- AMQP.Queue.declare(chan, q_base <> "_from_clients", [auto_delete: true]),
|
||||
from_clients <- [routing_key: "bot.#{device}.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.#"],
|
||||
: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 <> "_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}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,8 @@ defmodule Farmbot.CeleryScript.AST.Node.ChangeOwnership do
|
|||
email = pair_map["email"]
|
||||
secret = pair_map["secret"] |> Base.decode64!(padding: false, ignore: :whitespace)
|
||||
server = pair_map["server"] || get_config_value(:string, "authorization", "server")
|
||||
:ok = Farmbot.System.NervesHub.deconfigure()
|
||||
|
||||
case test_credentials(email, secret, server) do
|
||||
{:ok, _token} ->
|
||||
Logger.warn 1, "Farmbot is changing ownership to #{email} - #{server}."
|
||||
|
|
|
@ -5,15 +5,7 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
|
|||
|
||||
def execute(%{package: :farmbot_os}, _, env) do
|
||||
env = mutate_env(env)
|
||||
case Farmbot.System.Updates.check_updates() do
|
||||
{: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
|
||||
nerves_hub_updater(env)
|
||||
end
|
||||
|
||||
def execute(%{package: :arduino_firmware}, _, env) do
|
||||
|
@ -25,4 +17,11 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
|
|||
env = mutate_env(env)
|
||||
Farmbot.CeleryScript.AST.Node.UpdateFarmware.execute(args, [], env)
|
||||
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
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Farmbot.Project do
|
|||
@version Mix.Project.config[:version]
|
||||
@target Mix.Project.config[:target]
|
||||
@commit Mix.Project.config[:commit]
|
||||
@branch Mix.Project.config[:branch]
|
||||
@arduino_commit Mix.Project.config[:arduino_commit]
|
||||
@env Mix.env()
|
||||
|
||||
|
@ -15,6 +16,10 @@ defmodule Farmbot.Project do
|
|||
@compile {:inline, commit: 0}
|
||||
def commit, do: @commit
|
||||
|
||||
@doc "*#{@branch}*"
|
||||
@compile {:inline, branch: 0}
|
||||
def branch, do: @branch
|
||||
|
||||
@doc "*#{@arduino_commit}*"
|
||||
@compile {:inline, arduino_commit: 0}
|
||||
def arduino_commit, do: @arduino_commit
|
||||
|
|
|
@ -11,6 +11,7 @@ defmodule Farmbot.System.Init.FSCheckup do
|
|||
@version Farmbot.Project.version()
|
||||
@target Farmbot.Project.target()
|
||||
@env Farmbot.Project.env()
|
||||
System.put_env("NERVES_FW_VCS_IDENTIFIER", @ref)
|
||||
|
||||
@doc false
|
||||
def start_link(_, opts \\ []) do
|
||||
|
@ -68,7 +69,7 @@ defmodule Farmbot.System.Init.FSCheckup do
|
|||
try do
|
||||
Elixir.Logger.add_backend(LoggerBackendSqlite)
|
||||
catch
|
||||
:exit, r ->
|
||||
:exit, r ->
|
||||
Logger.error 1, "Could not start disk logging: #{inspect r}"
|
||||
Elixir.Logger.remove_backend(LoggerBackendSqlite)
|
||||
File.rm(Path.join([@data_path, "root", "debug_logs.sqlite3"]))
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,10 +1,11 @@
|
|||
defmodule Farmbot.System.Registry do
|
||||
@moduledoc "Farmbot System Global Registry"
|
||||
@reg FarmbotRegistry
|
||||
use GenServer
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc "Dispatch a global event from a namespace."
|
||||
|
|
|
@ -3,7 +3,6 @@ defmodule Farmbot.System.Supervisor do
|
|||
Supervises Platform specific stuff for Farmbot to operate
|
||||
"""
|
||||
use Supervisor
|
||||
import Farmbot.System.Init
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
|
@ -11,25 +10,11 @@ defmodule Farmbot.System.Supervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
before_init_children = [
|
||||
worker(Farmbot.System.Registry, []),
|
||||
worker(Farmbot.System.Init.KernelMods, [[], []]),
|
||||
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, []),
|
||||
children = [
|
||||
supervisor(Farmbot.System.Init.Suprevisor, []),
|
||||
worker(Farmbot.System.NervesHub, []),
|
||||
worker(Farmbot.EasterEggs, []),
|
||||
]
|
||||
|
||||
all_children = before_init_children ++ init_mods ++ after_init_children
|
||||
Supervisor.init(all_children, strategy: :one_for_all)
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,7 +68,7 @@ defmodule Farmbot.System do
|
|||
_, _ ->
|
||||
Logger.error 1, "Firmware unavailable. Can't emergency_lock"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
defp do_reset(reason) do
|
||||
|
@ -77,6 +77,7 @@ defmodule Farmbot.System do
|
|||
nil -> reboot("Escape factory reset: #{inspect reason}")
|
||||
{:ignore, reason} -> reboot(reason)
|
||||
_ ->
|
||||
Farmbot.System.NervesHub.deconfigure()
|
||||
path = Farmbot.Farmware.Installer.install_root_path()
|
||||
File.rm_rf(path)
|
||||
Ecto.drop()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
26
mix.exs
|
@ -2,10 +2,13 @@ defmodule Farmbot.Mixfile do
|
|||
use Mix.Project
|
||||
@target System.get_env("MIX_TARGET") || "host"
|
||||
@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
|
||||
System.cmd("git", ~w"rev-parse --verify HEAD") |> elem(0) |> String.trim()
|
||||
end
|
||||
defp commit, do: @commit
|
||||
|
||||
defp branch, do: @branch
|
||||
|
||||
defp arduino_commit do
|
||||
opts = [cd: "c_src/farmbot-arduino-firmware"]
|
||||
|
@ -28,6 +31,7 @@ defmodule Farmbot.Mixfile do
|
|||
version: @version,
|
||||
target: @target,
|
||||
commit: commit(),
|
||||
branch: branch(),
|
||||
arduino_commit: arduino_commit(),
|
||||
archives: [nerves_bootstrap: "~> 1.2"],
|
||||
build_embedded: Mix.env() == :prod,
|
||||
|
@ -58,13 +62,23 @@ defmodule Farmbot.Mixfile do
|
|||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
def application("host") do
|
||||
[
|
||||
mod: {Farmbot, []},
|
||||
extra_applications: [:logger, :eex, :ssl, :inets, :runtime_tools]
|
||||
]
|
||||
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
|
||||
[
|
||||
main: "building",
|
||||
|
@ -122,7 +136,8 @@ defmodule Farmbot.Mixfile do
|
|||
{:ring_logger, "~> 0.5"},
|
||||
{:bbmustache, "~> 1.6"},
|
||||
{:sqlite_ecto2, "~> 2.2"},
|
||||
{:logger_backend_sqlite, "~> 2.1"}
|
||||
{:logger_backend_sqlite, "~> 2.1"},
|
||||
{:nerves_hub_cli, "~> 0.4"}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -141,6 +156,7 @@ defmodule Farmbot.Mixfile do
|
|||
system(target) ++
|
||||
[
|
||||
{:nerves_runtime, "~> 0.8"},
|
||||
{:nerves_hub, github: "nerves-hub/nerves_hub", override: true},
|
||||
{:nerves_firmware, "~> 0.4"},
|
||||
{:nerves_firmware_ssh, "~> 0.3"},
|
||||
{:nerves_init_gadget, "~> 0.5", only: :dev},
|
||||
|
|
|
@ -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"},
|
||||
"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"},
|
||||
"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.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"},
|
||||
"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"},
|
||||
|
@ -48,12 +47,15 @@
|
|||
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "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"},
|
||||
"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_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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
|
@ -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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"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"},
|
||||
"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"},
|
||||
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "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_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_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_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"},
|
||||
|
@ -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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
|
@ -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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
}
|
||||
|
|
|
@ -23,13 +23,14 @@
|
|||
"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"},
|
||||
"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"},
|
||||
"gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
|
@ -42,9 +43,11 @@
|
|||
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "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"},
|
||||
"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_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_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"},
|
||||
|
@ -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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
|
@ -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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
"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"},
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
defmodule Nerves.Firmware do
|
||||
@moduledoc false
|
||||
@doc false
|
||||
def upgrade_and_finalize(_), do: :ok
|
||||
|
||||
def reboot, do: :ok
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -28,7 +28,6 @@ defmodule Farmbot.Target.Uevent do
|
|||
end
|
||||
|
||||
def handle_info({:system_registry, :global, new_reg}, %{} = old_reg) do
|
||||
require IEx; IEx.pry
|
||||
new_ttys = get_in(new_reg, [:state, "subsystems", "tty"]) || []
|
||||
old_ttys = get_in(old_reg, [:state, "subsystems", "tty"]) || []
|
||||
case new_ttys -- old_ttys do
|
||||
|
|
|
@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
ZL9J9sLK3GWa7YezB3v41Kxl0TmE15CARjApOGcNa1c=
|
|
@ -0,0 +1 @@
|
|||
qIbhsGnUVHGEu++RAss2U0caXySSwDBVKkK35sO0vVU=
|
|
@ -1,4 +1,6 @@
|
|||
-setcookie democookie
|
||||
+Bc
|
||||
-kernel shell_history enabled
|
||||
-mode embedded
|
||||
-sname farmbot
|
||||
-noshell
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#!/bin/bash
|
||||
TARGETS="rpi0 \
|
||||
rpi3 \
|
||||
bbb \
|
||||
host
|
||||
"
|
||||
for target in $TARGETS; do
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue