Merge pull request #363 from FarmBot/feature/migrate-json-config

Feature/migrate json config
This commit is contained in:
Connor Rigby 2017-11-29 09:04:59 -08:00 committed by GitHub
commit 6b45f6423b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 849 additions and 35 deletions

View file

@ -1 +1,2 @@
erlang 20.0
elixir 1.5.0

View file

@ -27,17 +27,23 @@ config :farmbot, data_path: "/root"
config :farmbot, :init, [
# Load consolidated protocols
Farmbot.Target.Protocols,
# Autodetects if a Arduino is plugged in and configures accordingly.
Farmbot.Firmware.UartHandler.AutoDetector,
Farmbot.Target.ConfigMigration.BeforeNetwork,
# Allows for first boot configuration.
Farmbot.Target.Bootstrap.Configurator,
# Start up Network
Farmbot.Target.Network,
# Wait for time time come up.
Farmbot.Target.Network.WaitForTime,
Farmbot.Target.ConfigMigration.AfterNetwork,
# Debug stuff
Farmbot.System.Debug,
Farmbot.Target.Uevent.Supervisor
@ -78,3 +84,7 @@ config :nerves_init_gadget,
config :bootloader,
init: [:nerves_runtime, :nerves_init_gadget],
app: :farmbot
if Mix.Project.config[:target] == "rpi3" do
config :nerves, :firmware, fwup_conf: "fwup_interim.conf"
end

518
fwup_interim.conf Normal file
View file

@ -0,0 +1,518 @@
# Firmware configuration file for the Raspberry Pi 3
#
# Firmware metadata
#
# All of these can be overriden using environment variables of the same name.
#
# Run 'fwup -m' to query values in a .fw file.
# Use 'fw_printenv' to query values on the target.
#
# These are used by Nerves libraries to introspect.
define(NERVES_FW_PRODUCT, "Nerves Firmware")
define(NERVES_FW_DESCRIPTION, "")
define(NERVES_FW_VERSION, "${NERVES_SDK_VERSION}")
define(NERVES_FW_PLATFORM, "rpi3")
define(NERVES_FW_ARCHITECTURE, "arm")
define(NERVES_FW_AUTHOR, "The Nerves Team")
define(NERVES_FW_DEVPATH, "/dev/mmcblk0")
define(NERVES_FW_APPLICATION_PART0_DEVPATH, "/dev/mmcblk0p3") # Linux part number is 1-based
define(NERVES_FW_APPLICATION_PART0_FSTYPE, "ext4")
define(NERVES_FW_APPLICATION_PART0_TARGET, "/root")
# Default paths if not specified via the commandline
define(ROOTFS, "${NERVES_SYSTEM}/images/rootfs.squashfs")
# This configuration file will create an image that has an MBR and the
# following 3 partitions:
#
# +----------------------------+
# | MBR |
# +----------------------------+
# | Firmware configuration data|
# | (formatted as uboot env) |
# +----------------------------+
# | p0*: Boot A (FAT32) |
# | zImage, bootcode.bin, |
# | config.txt, etc. |
# +----------------------------+
# | p0*: Boot B (FAT32) |
# +----------------------------+
# | p1*: Rootfs A (squashfs) |
# +----------------------------+
# | p1*: Rootfs B (squashfs) |
# +----------------------------+
# | p2: Application (ext4) |
# +----------------------------+
#
# The p0/p1 partition points to whichever of configurations A or B that is
# active.
#
# The image is sized to be less than 1 GB so that it fits on nearly any SDCard
# around. If you have a larger SDCard and need more space, feel free to bump
# the partition sizes below.
# The Raspberry Pi is incredibly picky on the partition sizes and in ways that
# I don't understand. Test changes one at a time to make sure that they boot.
# (Sizes are in 512 byte blocks)
define(UBOOT_ENV_OFFSET, 16)
define(UBOOT_ENV_COUNT, 16) # 8 KB
define(BOOT_A_PART_OFFSET, 63)
define(BOOT_A_PART_COUNT, 38630)
define-eval(BOOT_B_PART_OFFSET, "${BOOT_A_PART_OFFSET} + ${BOOT_A_PART_COUNT}")
define(BOOT_B_PART_COUNT, ${BOOT_A_PART_COUNT})
# Let the rootfs have room to grow up to 128 MiB and align it to the nearest 1
# MB boundary
define(ROOTFS_A_PART_OFFSET, 77324)
define(ROOTFS_A_PART_COUNT, 289044)
define-eval(ROOTFS_B_PART_OFFSET, "${ROOTFS_A_PART_OFFSET} + ${ROOTFS_A_PART_COUNT}")
define(ROOTFS_B_PART_COUNT, ${ROOTFS_A_PART_COUNT})
# Application partition. This partition can occupy all of the remaining space.
# Size it to fit the destination.
define-eval(APP_PART_OFFSET, "${ROOTFS_B_PART_OFFSET} + ${ROOTFS_B_PART_COUNT}")
define(APP_PART_COUNT, 1048576)
# Firmware archive metadata
meta-product = ${NERVES_FW_PRODUCT}
meta-description = ${NERVES_FW_DESCRIPTION}
meta-version = ${NERVES_FW_VERSION}
meta-platform = ${NERVES_FW_PLATFORM}
meta-architecture = ${NERVES_FW_ARCHITECTURE}
meta-author = ${NERVES_FW_AUTHOR}
meta-vcs-identifier = ${NERVES_FW_VCS_IDENTIFIER}
meta-misc = ${NERVES_FW_MISC}
# File resources are listed in the order that they are included in the .fw file
# This is important, since this is the order that they're written on a firmware
# update due to the event driven nature of the update system.
file-resource bootcode.bin {
host-path = "${NERVES_SYSTEM}/images/rpi-firmware/bootcode.bin"
}
file-resource fixup.dat {
host-path = "${NERVES_SYSTEM}/images/rpi-firmware/fixup.dat"
}
file-resource start.elf {
host-path = "${NERVES_SYSTEM}/images/rpi-firmware/start.elf"
}
file-resource config.txt {
host-path = "${NERVES_SYSTEM}/images/config.txt"
}
file-resource cmdline.txt {
host-path = "${NERVES_SYSTEM}/images/cmdline.txt"
}
file-resource zImage {
host-path = "${NERVES_SYSTEM}/images/zImage"
}
file-resource bcm2710-rpi-3-b.dtb {
host-path = "${NERVES_SYSTEM}/images/bcm2710-rpi-3-b.dtb"
}
file-resource bcm2710-rpi-cm3.dtb {
host-path = "${NERVES_SYSTEM}/images/bcm2710-rpi-cm3.dtb"
}
file-resource w1-gpio-pullup.dtbo {
host-path = "${NERVES_SYSTEM}/images/rpi-firmware/overlays/w1-gpio-pullup.dtbo"
}
file-resource rootfs.img {
host-path = ${ROOTFS}
# Error out if the rootfs size exceeds the partition size
assert-size-lte = ${ROOTFS_A_PART_COUNT}
}
mbr mbr-a {
partition 0 {
block-offset = ${BOOT_A_PART_OFFSET}
block-count = ${BOOT_A_PART_COUNT}
type = 0xc # FAT32
boot = true
}
partition 1 {
block-offset = ${ROOTFS_A_PART_OFFSET}
block-count = ${ROOTFS_A_PART_COUNT}
type = 0x83 # Linux
}
partition 2 {
block-offset = ${APP_PART_OFFSET}
block-count = ${APP_PART_COUNT}
type = 0x83 # Linux
}
# partition 3 is unused
}
mbr mbr-b {
partition 0 {
block-offset = ${BOOT_B_PART_OFFSET}
block-count = ${BOOT_B_PART_COUNT}
type = 0xc # FAT32
boot = true
}
partition 1 {
block-offset = ${ROOTFS_B_PART_OFFSET}
block-count = ${ROOTFS_B_PART_COUNT}
type = 0x83 # Linux
}
partition 2 {
block-offset = ${APP_PART_OFFSET}
block-count = ${APP_PART_COUNT}
type = 0x83 # Linux
}
# partition 3 is unused
}
# Location where installed firmware information is stored.
# While this is called "u-boot", u-boot isn't involved in this
# setup. It just provides a convenient key/value store format.
uboot-environment uboot-env {
block-offset = ${UBOOT_ENV_OFFSET}
block-count = ${UBOOT_ENV_COUNT}
}
# This firmware task writes everything to the destination media
task complete {
# Only match if not mounted
require-unmounted-destination = true
on-init {
mbr_write(mbr-a)
uboot_clearenv(uboot-env)
uboot_setenv(uboot-env, "nerves_fw_active", "a")
uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
fat_mkfs(${BOOT_A_PART_OFFSET}, ${BOOT_A_PART_COUNT})
fat_setlabel(${BOOT_A_PART_OFFSET}, "BOOT-A")
fat_mkdir(${BOOT_A_PART_OFFSET}, "overlays")
}
on-resource config.txt { fat_write(${BOOT_A_PART_OFFSET}, "config.txt") }
on-resource cmdline.txt { fat_write(${BOOT_A_PART_OFFSET}, "cmdline.txt") }
on-resource bootcode.bin { fat_write(${BOOT_A_PART_OFFSET}, "bootcode.bin") }
on-resource start.elf { fat_write(${BOOT_A_PART_OFFSET}, "start.elf") }
on-resource fixup.dat { fat_write(${BOOT_A_PART_OFFSET}, "fixup.dat") }
on-resource zImage { fat_write(${BOOT_A_PART_OFFSET}, "zImage") }
on-resource bcm2710-rpi-3-b.dtb { fat_write(${BOOT_A_PART_OFFSET}, "bcm2710-rpi-3-b.dtb") }
on-resource bcm2710-rpi-cm3.dtb { fat_write(${BOOT_A_PART_OFFSET}, "bcm2710-rpi-cm3.dtb") }
on-resource w1-gpio-pullup.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/w1-gpio-pullup.dtbo") }
on-resource rootfs.img {
# write to the first rootfs partition
raw_write(${ROOTFS_A_PART_OFFSET})
}
on-finish {
# Clear out any old data in the B partition that might be mistaken for
# a file system. This is mostly to avoid confusion in humans when
# reprogramming SDCards with unknown contents.
raw_memset(${BOOT_B_PART_OFFSET}, 256, 0xff)
raw_memset(${ROOTFS_B_PART_OFFSET}, 256, 0xff)
# Invalidate the application data partition so that it is guaranteed to
# trigger the corrupt filesystem detection code on first boot and get
# formatted. If this isn't done and an old SDCard is reused, the
# application data could be in a weird state.
raw_memset(${APP_PART_OFFSET}, 256, 0xff)
}
}
task upgrade.a {
# This task upgrades the A partition
require-partition-offset(1, ${ROOTFS_B_PART_OFFSET})
on-init {
info("Initializing u-boot")
uboot_clearenv(uboot-env)
uboot_setenv(uboot-env, "nerves_fw_active", "a")
uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
info("Upgrading partition A")
# Clear some firmware information just in case this update gets
# interrupted midway.
uboot_unsetenv(uboot-env, "a.nerves_fw_version")
# Reset the previous contents of the A boot partition
fat_mkfs(${BOOT_A_PART_OFFSET}, ${BOOT_A_PART_COUNT})
fat_setlabel(${BOOT_A_PART_OFFSET}, "BOOT-A")
fat_mkdir(${BOOT_A_PART_OFFSET}, "overlays")
# Indicate that the entire partition can be cleared
trim(${ROOTFS_A_PART_OFFSET}, ${ROOTFS_A_PART_COUNT})
}
# Write the new boot partition files and rootfs. The MBR still points
# to the B partition, so an error or power failure during this part
# won't hurt anything.
on-resource config.txt { fat_write(${BOOT_A_PART_OFFSET}, "config.txt") }
on-resource cmdline.txt { fat_write(${BOOT_A_PART_OFFSET}, "cmdline.txt") }
on-resource bootcode.bin { fat_write(${BOOT_A_PART_OFFSET}, "bootcode.bin") }
on-resource start.elf { fat_write(${BOOT_A_PART_OFFSET}, "start.elf") }
on-resource fixup.dat { fat_write(${BOOT_A_PART_OFFSET}, "fixup.dat") }
on-resource zImage { fat_write(${BOOT_A_PART_OFFSET}, "zImage") }
on-resource bcm2710-rpi-3-b.dtb { fat_write(${BOOT_A_PART_OFFSET}, "bcm2710-rpi-3-b.dtb") }
on-resource bcm2710-rpi-cm3.dtb { fat_write(${BOOT_A_PART_OFFSET}, "bcm2710-rpi-cm3.dtb") }
on-resource w1-gpio-pullup.dtbo { fat_write(${BOOT_A_PART_OFFSET}, "overlays/w1-gpio-pullup.dtbo") }
on-resource rootfs.img { raw_write(${ROOTFS_A_PART_OFFSET}) }
on-finish {
# Update firmware metadata
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
# Switch over to boot the new firmware
uboot_setenv(uboot-env, "nerves_fw_active", "a")
mbr_write(mbr-a)
}
on-error {
}
}
task upgrade.b {
# This task upgrades the B partition
require-partition-offset(1, ${ROOTFS_A_PART_OFFSET})
on-init {
info("Initializing u-boot")
uboot_clearenv(uboot-env)
uboot_setenv(uboot-env, "nerves_fw_active", "a")
uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
info("Upgrading partition B")
# Clear some firmware information just in case this update gets
# interrupted midway.
uboot_unsetenv(uboot-env, "b.nerves_fw_version")
# Reset the previous contents of the B boot partition
fat_mkfs(${BOOT_B_PART_OFFSET}, ${BOOT_B_PART_COUNT})
fat_setlabel(${BOOT_B_PART_OFFSET}, "BOOT-B")
fat_mkdir(${BOOT_B_PART_OFFSET}, "overlays")
trim(${ROOTFS_B_PART_OFFSET}, ${ROOTFS_B_PART_COUNT})
}
# Write the new boot partition files and rootfs. The MBR still points
# to the A partition, so an error or power failure during this part
# won't hurt anything.
on-resource config.txt { fat_write(${BOOT_B_PART_OFFSET}, "config.txt") }
on-resource cmdline.txt { fat_write(${BOOT_B_PART_OFFSET}, "cmdline.txt") }
on-resource bootcode.bin { fat_write(${BOOT_B_PART_OFFSET}, "bootcode.bin") }
on-resource start.elf { fat_write(${BOOT_B_PART_OFFSET}, "start.elf") }
on-resource fixup.dat { fat_write(${BOOT_B_PART_OFFSET}, "fixup.dat") }
on-resource zImage { fat_write(${BOOT_B_PART_OFFSET}, "zImage") }
on-resource bcm2710-rpi-3-b.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-3-b.dtb") }
on-resource bcm2710-rpi-cm3.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-cm3.dtb") }
on-resource w1-gpio-pullup.dtbo { fat_write(${BOOT_B_PART_OFFSET}, "overlays/w1-gpio-pullup.dtbo") }
on-resource rootfs.img { raw_write(${ROOTFS_B_PART_OFFSET}) }
on-finish {
# Update firmware metadata
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "b.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "b.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "b.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "b.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "b.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "b.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "b.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "b.nerves_fw_misc", ${NERVES_FW_MISC})
# Switch over to boot the new firmware
uboot_setenv(uboot-env, "nerves_fw_active", "b")
mbr_write(mbr-b)
}
on-error {
}
}
task upgrade.unexpected {
# This task upgrades the B partition
require-partition-offset(1, ${ROOTFS_A_PART_OFFSET})
on-init {
info("Initializing u-boot")
uboot_clearenv(uboot-env)
uboot_setenv(uboot-env, "nerves_fw_active", "a")
uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
info("Upgrading partition B")
# Clear some firmware information just in case this update gets
# interrupted midway.
uboot_unsetenv(uboot-env, "b.nerves_fw_version")
# Reset the previous contents of the B boot partition
fat_mkfs(${BOOT_B_PART_OFFSET}, ${BOOT_B_PART_COUNT})
fat_setlabel(${BOOT_B_PART_OFFSET}, "BOOT-B")
fat_mkdir(${BOOT_B_PART_OFFSET}, "overlays")
trim(${ROOTFS_B_PART_OFFSET}, ${ROOTFS_B_PART_COUNT})
}
# Write the new boot partition files and rootfs. The MBR still points
# to the A partition, so an error or power failure during this part
# won't hurt anything.
on-resource config.txt { fat_write(${BOOT_B_PART_OFFSET}, "config.txt") }
on-resource cmdline.txt { fat_write(${BOOT_B_PART_OFFSET}, "cmdline.txt") }
on-resource bootcode.bin { fat_write(${BOOT_B_PART_OFFSET}, "bootcode.bin") }
on-resource start.elf { fat_write(${BOOT_B_PART_OFFSET}, "start.elf") }
on-resource fixup.dat { fat_write(${BOOT_B_PART_OFFSET}, "fixup.dat") }
on-resource zImage { fat_write(${BOOT_B_PART_OFFSET}, "zImage") }
on-resource bcm2710-rpi-3-b.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-3-b.dtb") }
on-resource bcm2710-rpi-cm3.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-cm3.dtb") }
on-resource w1-gpio-pullup.dtbo { fat_write(${BOOT_B_PART_OFFSET}, "overlays/w1-gpio-pullup.dtbo") }
on-resource rootfs.img { raw_write(${ROOTFS_B_PART_OFFSET}) }
on-finish {
# Update firmware metadata
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "b.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "b.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "b.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "b.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "b.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "b.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "b.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "b.nerves_fw_misc", ${NERVES_FW_MISC})
# Switch over to boot the new firmware
uboot_setenv(uboot-env, "nerves_fw_active", "b")
mbr_write(mbr-b)
}
on-error {
}
}
task upgrade.wrongplatform {
# This task upgrades the B partition
require-partition-offset(1, ${ROOTFS_A_PART_OFFSET})
on-init {
info("Initializing u-boot")
uboot_clearenv(uboot-env)
uboot_setenv(uboot-env, "nerves_fw_active", "a")
uboot_setenv(uboot-env, "nerves_fw_devpath", ${NERVES_FW_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "a.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "a.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "a.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "a.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "a.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "a.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "a.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "a.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "a.nerves_fw_misc", ${NERVES_FW_MISC})
info("Upgrading partition B")
# Clear some firmware information just in case this update gets
# interrupted midway.
uboot_unsetenv(uboot-env, "b.nerves_fw_version")
# Reset the previous contents of the B boot partition
fat_mkfs(${BOOT_B_PART_OFFSET}, ${BOOT_B_PART_COUNT})
fat_setlabel(${BOOT_B_PART_OFFSET}, "BOOT-B")
fat_mkdir(${BOOT_B_PART_OFFSET}, "overlays")
trim(${ROOTFS_B_PART_OFFSET}, ${ROOTFS_B_PART_COUNT})
}
# Write the new boot partition files and rootfs. The MBR still points
# to the A partition, so an error or power failure during this part
# won't hurt anything.
on-resource config.txt { fat_write(${BOOT_B_PART_OFFSET}, "config.txt") }
on-resource cmdline.txt { fat_write(${BOOT_B_PART_OFFSET}, "cmdline.txt") }
on-resource bootcode.bin { fat_write(${BOOT_B_PART_OFFSET}, "bootcode.bin") }
on-resource start.elf { fat_write(${BOOT_B_PART_OFFSET}, "start.elf") }
on-resource fixup.dat { fat_write(${BOOT_B_PART_OFFSET}, "fixup.dat") }
on-resource zImage { fat_write(${BOOT_B_PART_OFFSET}, "zImage") }
on-resource bcm2710-rpi-3-b.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-3-b.dtb") }
on-resource bcm2710-rpi-cm3.dtb { fat_write(${BOOT_B_PART_OFFSET}, "bcm2710-rpi-cm3.dtb") }
on-resource w1-gpio-pullup.dtbo { fat_write(${BOOT_B_PART_OFFSET}, "overlays/w1-gpio-pullup.dtbo") }
on-resource rootfs.img { raw_write(${ROOTFS_B_PART_OFFSET}) }
on-finish {
# Update firmware metadata
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_devpath", ${NERVES_FW_APPLICATION_PART0_DEVPATH})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_fstype", ${NERVES_FW_APPLICATION_PART0_FSTYPE})
uboot_setenv(uboot-env, "b.nerves_fw_application_part0_target", ${NERVES_FW_APPLICATION_PART0_TARGET})
uboot_setenv(uboot-env, "b.nerves_fw_product", ${NERVES_FW_PRODUCT})
uboot_setenv(uboot-env, "b.nerves_fw_description", ${NERVES_FW_DESCRIPTION})
uboot_setenv(uboot-env, "b.nerves_fw_version", ${NERVES_FW_VERSION})
uboot_setenv(uboot-env, "b.nerves_fw_platform", ${NERVES_FW_PLATFORM})
uboot_setenv(uboot-env, "b.nerves_fw_architecture", ${NERVES_FW_ARCHITECTURE})
uboot_setenv(uboot-env, "b.nerves_fw_author", ${NERVES_FW_AUTHOR})
uboot_setenv(uboot-env, "b.nerves_fw_vcs_identifier", ${NERVES_FW_VCS_IDENTIFIER})
uboot_setenv(uboot-env, "b.nerves_fw_misc", ${NERVES_FW_MISC})
# Switch over to boot the new firmware
uboot_setenv(uboot-env, "nerves_fw_active", "b")
mbr_write(mbr-b)
}
on-error {
}
}

View file

@ -24,23 +24,44 @@ defmodule Farmbot.Bootstrap.Authorization do
# this is the default authorize implementation.
# It gets overwrote in the Test Environment.
@doc "Authorizes with the farmbot api."
def authorize(email, password, server) do
with {:ok, rsa_key} <- fetch_rsa_key(server),
{:ok, payload} <- build_payload(email, password, rsa_key),
{:ok, resp} <- request_token(server, payload),
{:ok, body} <- Poison.decode(resp),
{:ok, map} <- Map.fetch(body, "token") do
Map.fetch(map, "encoded")
else
:error -> {:error, "unknown error."}
{:error, :invalid, _} -> authorize(email, password, server)
# If we got maintance mode, a 5xx error etc, just sleep for a few seconds
# and try again.
{:ok, {{_, code, _}, _, _}} ->
Logger.error 1, "Failed to authorize due to server error: #{code}"
Process.sleep(5000)
authorize(email, password, server)
err -> err
def authorize(email, password_or_secret, server) do
case Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "first_boot") do
true ->
with {:ok, rsa_key} <- fetch_rsa_key(server),
{:ok, payload} <- build_payload(email, password_or_secret, rsa_key),
{:ok, resp} <- request_token(server, payload),
{:ok, body} <- Poison.decode(resp),
{:ok, map} <- Map.fetch(body, "token") do
Farmbot.System.ConfigStorage.update_config_value(:bool, "settings", "first_boot", false)
Map.fetch(map, "encoded")
else
:error -> {:error, "unknown error."}
{:error, :invalid, _} -> authorize(email, password_or_secret, server)
# If we got maintance mode, a 5xx error etc, just sleep for a few seconds
# and try again.
{:ok, {{_, code, _}, _, _}} ->
Logger.error 1, "Failed to authorize due to server error: #{code}"
Process.sleep(5000)
authorize(email, password_or_secret, server)
err -> err
end
false ->
with {:ok, payload} <- build_payload(password_or_secret),
{:ok, resp} <- request_token(server, payload),
{:ok, body} <- Poison.decode(resp),
{:ok, map} <- Map.fetch(body, "token") do
Map.fetch(map, "encoded")
else
:error -> {:error, "unknown error."}
{:error, :invalid, _} -> authorize(email, password_or_secret, server)
# If we got maintance mode, a 5xx error etc, just sleep for a few seconds
# and try again.
{:ok, {{_, code, _}, _, _}} ->
Logger.error 1, "Failed to authorize due to server error: #{code}"
Process.sleep(5000)
authorize(email, password_or_secret, server)
err -> err
end
end
end
@ -56,9 +77,14 @@ defmodule Farmbot.Bootstrap.Authorization do
%{email: email, password: password, id: UUID.uuid1(), version: 1}
|> Poison.encode!()
|> RSA.encrypt({:public, rsa_key})
|> Base.encode64()
Farmbot.System.ConfigStorage.update_config_value(:string, "authorization", "password", secret)
%{user: %{credentials: secret}} |> Poison.encode()
%{user: %{credentials: secret |> Base.encode64()}} |> Poison.encode()
end
defp build_payload(secret) do
user = %{credentials: secret |> :base64.encode_to_string |> to_string}
Poison.encode(%{user: user})
end
defp request_token(server, payload) do

View file

@ -223,9 +223,11 @@ defmodule Farmbot.BotState do
end
def init([]) do
settings = Farmbot.System.ConfigStorage.get_config_as_map()["settings"]
user_env = Poison.decode!(settings["user_env"])
{
:producer_consumer,
struct(__MODULE__, configuration: Farmbot.System.ConfigStorage.get_config_as_map()["settings"]),
struct(__MODULE__, configuration: Map.delete(settings, "user_env"), user_env: user_env),
subscribe_to: [Farmbot.Firmware, Farmbot.System.ConfigStorage.Dispatcher],
dispatcher: GenStage.BroadcastDispatcher
}
@ -272,9 +274,13 @@ defmodule Farmbot.BotState do
end
def handle_call({:set_user_env, key, val}, _, state) do
new_user_env = Map.put(state.user_env, key, val)
new_state = %{state | user_env: new_user_env}
{:reply, :ok, [new_state], new_state}
new_user_env = Map.merge(state.user_env, %{to_string(key) => val})
case Poison.encode(new_user_env) do
{:ok, encoded} ->
Farmbot.System.ConfigStorage.update_config_value(:string, "settings", "user_env", encoded)
{:reply, :ok, [], state}
_ -> {:reply, :error, [], state}
end
end
def handle_call(:get_user_env, _from, state) do
@ -325,6 +331,12 @@ defmodule Farmbot.BotState do
defp do_handle([], state), do: state
defp do_handle([{:config, "settings", "user_env", val} | rest], state) do
new_env = Map.merge(state.user_env, Poison.decode!(val))
new_state = %{state | user_env: new_env}
do_handle(rest, new_state)
end
defp do_handle([{:config, "settings", key, val} | rest], state) do
new_config = Map.put(state.configuration, key, val)
new_state = %{state | configuration: new_config}

View file

@ -1,6 +1,7 @@
defmodule Farmbot.Firmware.UartHandler.Update do
@moduledoc false
use Farmbot.Logger
def maybe_update_firmware(hardware \\ nil) do
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
hardware = case hardware do

View file

@ -7,7 +7,8 @@ defmodule Farmbot.Jwt do
:iss,
:mqtt,
:vhost,
:os_update_server
:os_update_server,
:interim_email
]
@typedoc "Type def for Farmbot Web Token."
@ -18,6 +19,7 @@ defmodule Farmbot.Jwt do
mqtt: binary,
os_update_server: binary,
vhost: binary,
interim_email: binary
}
@doc "Decode a token."

View file

@ -117,7 +117,7 @@ defmodule Farmbot.System.Updates.SlackUpdater do
end
def init(nil) do
Logger.warn(3, "Not setting up slack (No token)")
Logger.warn(3, "Not setting up slack (No slack token)")
:ignore
end

View file

@ -22,7 +22,9 @@ defmodule Farmbot.Host.Bootstrap.Configurator do
pass = Application.get_env(:farmbot, :authorization)[:password] || raise error("password")
server = Application.get_env(:farmbot, :authorization)[:server] || raise error("server")
ConfigStorage.update_config_value(:string, "authorization", "email", email)
ConfigStorage.update_config_value(:string, "authorization", "password", pass)
if ConfigStorage.get_config_value(:bool, "settings", "first_boot") do
ConfigStorage.update_config_value(:string, "authorization", "password", pass)
end
ConfigStorage.update_config_value(:string, "authorization", "server", server)
ConfigStorage.update_config_value(:string, "authorization", "token", nil)
:ignore

View file

@ -4,6 +4,7 @@ defmodule Farmbot.Host.SystemTasks do
@behaviour Farmbot.System
def factory_reset(reason) do
IO.inspect reason
shutdown(reason)
end

View file

@ -41,13 +41,6 @@ defmodule Farmbot.Target.Bootstrap.Configurator do
end
end
@data_path Application.get_env(:farmbot, :data_path) || Mix.raise("Unconfigured data path.")
def flag_configured do
check_file = Path.join(@data_path, "configured")
File.write(check_file, "configured")
end
def init(_) do
first_boot? = ConfigStorage.get_config_value(:bool, "settings", "first_boot")
if first_boot? do

View file

@ -0,0 +1,84 @@
defmodule Farmbot.Target.ConfigMigration.AfterNetwork do
@moduledoc "Finish the migration. Before authorization but after network."
use GenServer
alias Farmbot.System.ConfigStorage
use Farmbot.Logger
@data_path Application.get_env(:farmbot, :data_path)
@doc false
def start_link(_, _) do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
old_config_json_file = Path.join(@data_path, "config.json")
if File.exists?(old_config_json_file) do
server = ConfigStorage.get_config_value(:string, "authorization", "server")
secret = ConfigStorage.get_config_value(:string, "authorization", "password")
case authorize(server, secret) do
{:ok, encoded} when is_binary(encoded) ->
{:ok, %{interim_email: email}} = Farmbot.Jwt.decode(encoded)
ConfigStorage.update_config_value(:string, "authorization", "token", encoded)
ConfigStorage.update_config_value(:string, "authorization", "email", email)
Logger.success 1, "Successfully migrated secret."
File.rm(old_config_json_file)
:ignore
{:error, reason} ->
{:stop, reason}
end
else
:ignore
end
end
def authorize(server, secret) do
with {:ok, payload} <- build_payload(secret),
{:ok, resp} <- request_token(server, payload),
{:ok, body} <- Poison.decode(resp),
{:ok, map} <- Map.fetch(body, "token") do
Map.fetch(map, "encoded")
else
:error -> {:error, "unknown error."}
{:error, :invalid, _} -> authorize(server, secret)
# If we got maintance mode, a 5xx error etc, just sleep for a few seconds
# and try again.
{:ok, {{_, code, _}, _, _}} ->
Logger.error 1, "Failed to authorize due to server error: #{code}"
Process.sleep(5000)
authorize(server, secret)
err -> err
end
end
defp build_payload(secret) do
user = %{credentials: secret |> :base64.encode_to_string |> to_string}
Poison.encode(%{user: user})
end
defp request_token(server, payload) do
request = {
'#{server}/api/tokens',
['UserAgent', 'FarmbotOSBootstrap'],
'application/json',
payload
}
case :httpc.request(:post, request, [], []) do
{:ok, {{_, 200, _}, _, resp}} ->
{:ok, resp}
# if the error is a 4xx code, it was a failed auth.
{:ok, {{_, code, _}, _, resp}} when code > 399 and code < 500 ->
{
:error,
"Failed to authorize with the Farmbot web application at: #{server} with code: #{code}: #{inspect resp}"
}
# if the error is not 2xx and not 4xx, probably maintance mode.
{:ok, _} = err -> err
{:error, error} -> {:error, error}
end
end
end

View file

@ -0,0 +1,163 @@
defmodule Farmbot.Target.ConfigMigration.BeforeNetwork do
@moduledoc "Init module for migrating the old JSON based config."
use GenServer
alias Farmbot.System.ConfigStorage
use Farmbot.Logger
@data_path Application.get_env(:farmbot, :data_path)
@doc false
def start_link(_, _) do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init([]) do
old_farmware_dir = Path.join(@data_path, "farmware")
old_config_json_file = Path.join(@data_path, "config.json")
old_secret_file = Path.join(@data_path, "secret")
if File.exists?(old_config_json_file) do
File.rm_rf(old_farmware_dir)
Logger.busy 1, "Migrating json based config."
with :ok <- migrate_config_file(old_config_json_file),
:ok <- migrate_secret(old_secret_file),
_ <- migrate_fw()
do
Logger.success 1, "Successfully migrated."
:ignore
else
{:error, step, sub_step, reason} ->
msg = "Migration failed at step: #{step} substep: #{sub_step} reason: #{inspect reason}"
Logger.error 1, msg
{:stop, msg}
end
else
:ignore
end
end
def migrate_fw do
hw = ConfigStorage.get_config_value(:string, "settings", "firmware_hardware")
Farmbot.Firmware.UartHandler.Update.maybe_update_firmware(hw)
end
def migrate_config_file(filename) do
with {:file_read, {:ok, file}} <- {:file_read, File.read(filename)},
{:json_decode, {:ok, data}} <- {:json_decode, Poison.decode(file)},
{:authorization, {:ok, auth}} <- {:authorization, Map.fetch(data, "authorization")},
{:authorization, :ok} <- {:authorization, migrate_auth(auth)},
{:configuration, {:ok, conf}} <- {:configuration, Map.fetch(data, "configuration")},
{:configuration, :ok} <- {:configuration, migrate_configuration(conf)},
{:hardware, {:ok, hw }} <- {:hardware, Map.fetch(data, "hardware")},
{:hardware, :ok} <- {:hardware, migrate_hw(hw)},
{:network, {:ok, net }} <- {:network, Map.fetch(data, "network")},
{:network, :ok} <- {:network, migrate_network(net)} do
Logger.success 1, "JSON config migrated"
:ok
else
{step, {:error, reason}} -> {:error, :migrate_config_file, step, reason}
end
end
def migrate_secret(filename) do
with {:ok, bin_term} <- File.read(filename) do
bin = :erlang.binary_to_term(bin_term)
ConfigStorage.update_config_value(:string, "authorization", "password", bin)
File.rm(filename)
else
{:error, reason} ->
{:error, :migrate_secret_file, :file_read, reason}
end
end
defp migrate_auth(%{"server" => server}) do
ConfigStorage.update_config_value(:string, "authorization", "server", server)
end
defp migrate_configuration(%{"firmware_hardware" => hw,
"first_party_farmware" => fpf,
"os_auto_update" => os_auto_update,
"timezone" => tz, "user_env" => user_env})
do
import ConfigStorage, only: [update_config_value: 4]
with {:first_party_farmware, :type_check, true} <- {:first_party_farmware, :type_check, is_boolean(fpf)},
{:first_party_farmware, :update_config_value, :ok} <- {:first_party_farmware, :update_config_value, update_config_value(:bool, "settings", "first_party_farmware", fpf)},
{:firmware_hardware, :type_check, true} <- {:firmware_hardware, :type_check, hw in ["arduino", "arduino"]},
{:firmware_hardware, :update_config_value, :ok} <- {:firmware_hardware, :update_config_value, update_config_value(:string, "settings", "firmware_hardware", hw)},
{:os_auto_update, :type_check, true} <- {:os_auto_update, :type_check, is_boolean(os_auto_update)},
{:os_auto_update, :update_config_value, :ok} <- {:os_auto_update, :update_config_value, update_config_value(:bool, "settings", "os_auto_update", os_auto_update)},
{:timezone, :type_check, true} <- {:timezone, :type_check, (is_binary(tz) or is_nil(tz))},
{:timezone, :update_config_value, :ok} <- {:timezone, :update_config_value, update_config_value(:string, "settings", "timezone", tz)},
{:user_env, :type_check, true} <- {:user_env, :type_check, is_map(user_env)},
{:user_env, :type_cast, {:ok, user_env_enc}} <- {:user_env, :type_cast, Poison.encode(user_env)},
{:user_env, :update_config_value, :ok} <- {:user_env, :update_config_value, update_config_value(:string, "settings", "user_env", user_env_enc)},
{:first_boot, :update_config_value, :ok} <- {:first_boot, :update_config_value, update_config_value(:bool, "settings", "first_boot", false)} do
Logger.success 1, "Configuration data from jsono file was merged."
:ok
else
{step, :type_check, _} -> {:error, "#{step} failed type checking."}
{step, sub_step, err} ->
{:error, reason} = err
{:error, "#{step} failed at #{sub_step} reason: #{inspect reason}"}
end
end
defp migrate_hw(%{"params" => params}) do
expected_keys = struct(Farmbot.BotState).mcu_params |> Enum.map(fn({key, _}) -> Atom.to_string(key) end)
migrated = Map.take(params, expected_keys)
if Enum.all?(migrated, fn({param, val}) ->
cond do
is_integer(val) ->
ConfigStorage.update_config_value(:float, "hardware_params", param, (val / 1))
is_float(val) ->
ConfigStorage.update_config_value(:float, "hardware_params", param, val)
is_nil(val) -> :ok
end
end) do
:ok
else
{:error, "Failed to migrate params"}
end
end
defp migrate_network(%{"interfaces" => ifaces}) do
case do_migrate_network(Map.to_list(ifaces)) do
[] -> {:error, "No networks were migrated."}
_ -> :ok
end
end
defp do_migrate_network(ifaces, acc \\ [])
defp do_migrate_network([], acc), do: acc
defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wired"}} | rest], acc) do
%ConfigStorage.NetworkInterface{name: ifname, type: "wired", ipv4_method: "dhcp"}
|> ConfigStorage.insert()
|> case do
{:ok, res} -> do_migrate_network(rest, [res | acc])
_ -> do_migrate_network(rest, acc)
end
end
defp do_migrate_network([{ifname, %{"default" => "dhcp", "type" => "wireless",
"settings" => %{"key_mgmt" => "WPA-PSK", "psk" => psk, "ssid" => ssid}}} | rest], acc) do
%ConfigStorage.NetworkInterface{
name: ifname,
type: "wireless",
ssid: ssid,
psk: psk,
security: "WPA-PSK",
ipv4_method: "dhcp" }
|> ConfigStorage.insert()
|> case do
{:ok, res} -> do_migrate_network(rest, [res | acc])
_ -> do_migrate_network(rest, acc)
end
end
defp do_migrate_network([_ | rest], acc) do
do_migrate_network(rest, acc)
end
end

View file

@ -138,7 +138,8 @@ defmodule Farmbot.System.ConfigStorage.Migrations.SeedGroups do
create_value(BoolValue, false) |> create_config(group_id, "auto_sync")
create_value(StringValue, nil) |> create_config(group_id, "firmware_hardware")
create_value(StringValue, nil) |> create_config(group_id, "timezone")
create_value(FloatValue, nil) |> create_config(group_id, "network_not_found_timer")
create_value(FloatValue, nil) |> create_config(group_id, "network_not_found_timer")
create_value(StringValue, "{}") |> create_config(group_id, "user_env")
fpf_url = Application.get_env(:farmbot, :farmware)[:first_part_farmware_manifest_url]
create_value(StringValue, fpf_url) |> create_config(group_id, "first_party_farmware_url")
end

View file

@ -1 +1 @@
iex --sname farmbot --cookie democookie --remsh farmbot@farmbot-eb30
iex --sname farmbot --cookie democookie --remsh farmbot@farmbot-7e4b