# Revert firmware for a Vultr Server Image
#
# To use:
#   1. Run `fwup -c -f fwup-revert.conf -o revert.fw` and copy revert.fw to
#      the device. This is done automatically as part of the Nerves system
#      build process. The file is stored in `/usr/share/fwup/revert.fw`.
#   2. On the device, run `fwup -t revert revert.fw -d $NERVES_FW_DEVPATH`. If
#      it succeeds, reboot. If not, then it's possible that there isn't a previous
#      firmware or the metadata about what's stored where is corrupt or out of
#      sync.
#
# It is critical that this is kept in sync with the main fwup.conf.

require-fwup-version="0.19.0"

#
# 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 Vultr Image")
define(NERVES_FW_DESCRIPTION, "")
define(NERVES_FW_VERSION, "${NERVES_SDK_VERSION}")
define(NERVES_FW_PLATFORM, "vultr")
define(NERVES_FW_ARCHITECTURE, "x86_64")
define(NERVES_FW_AUTHOR, "The Nerves Team")

define(NERVES_FW_DEVPATH, "/dev/vda")
define(NERVES_FW_APPLICATION_PART0_DEVPATH, "/dev/vda4") # Linux part number is 1-based
define(NERVES_FW_APPLICATION_PART0_FSTYPE, "ext4")
define(NERVES_FW_APPLICATION_PART0_TARGET, "/root")
define(NERVES_PROVISIONING, "${NERVES_SYSTEM}/images/fwup_include/provisioning.conf")

# 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 w/ Grub boot code      |
# +----------------------------+
# | grub.img (<256K)           |
# +----------------------------+
# | Firmware configuration data|
# | (formatted as uboot env)   |
# +----------------------------+
# | Boot partition (FAT32)     |
# | /boot/grub/grub.cfg        |
# | /boot/grub/grubenv         |
# +----------------------------+
# | p2: Rootfs A (squashfs)    |
# +----------------------------+
# | p3: Rootfs B (squashfs)    |
# +----------------------------+
# | p4: Application (ext4)     |
# +----------------------------+

define(UBOOT_ENV_OFFSET, 2048)
define(UBOOT_ENV_COUNT, 16)  # 8 KB

# The boot partition contains the bootloader configuration
# 16 MB should be plenty for now.
define(BOOT_PART_OFFSET, 4096)
define(BOOT_PART_COUNT, 31232)

# Let the rootfs have room to grow up to 256 MiB
define-eval(ROOTFS_A_PART_OFFSET, "${BOOT_PART_OFFSET} + ${BOOT_PART_COUNT}")
define(ROOTFS_A_PART_COUNT, 524288)
define-eval(ROOTFS_B_PART_OFFSET, "${ROOTFS_A_PART_OFFSET} + ${ROOTFS_A_PART_COUNT}")
define(ROOTFS_B_PART_COUNT, ${ROOTFS_A_PART_COUNT})

# Application data partition (10 GiB). This can be enlarged
# to fill the entire volume.
define-eval(APP_PART_OFFSET, "${ROOTFS_B_PART_OFFSET} + ${ROOTFS_B_PART_COUNT}")
define(APP_PART_COUNT, 20971520)

# 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-resource grubenv_a {
    host-path = "${NERVES_SYSTEM}/images/grubenv_a"
}
file-resource grubenv_b {
    host-path = "${NERVES_SYSTEM}/images/grubenv_b"
}

# 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}
}

task revert.a {
    # This task reverts to the A partition, so check that we're running on B
    require-path-on-device("/", "/dev/vda3")

    # Verify that partition A has the expected platform/architecture
    require-uboot-variable(uboot-env, "a.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
    require-uboot-variable(uboot-env, "a.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")

    on-resource grubenv_a {
        info("Reverting to partition A")

	# Switch over
        uboot_setenv(uboot-env, "nerves_fw_active", "a")
        fat_write(${BOOT_PART_OFFSET}, "/boot/grub/grubenv")
    }
}

task revert.b {
    # This task reverts to the B partition, so check that we're running on A
    require-path-on-device("/", "/dev/vda2")

    # Verify that partition B has the expected platform/architecture
    require-uboot-variable(uboot-env, "b.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
    require-uboot-variable(uboot-env, "b.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")

    on-resource grubenv_b {
        info("Reverting to partition B")

	# Switch over
        uboot_setenv(uboot-env, "nerves_fw_active", "b")
        fat_write(${BOOT_PART_OFFSET}, "/boot/grub/grubenv")
    }
}

task revert.unexpected.a {
    require-uboot-variable(uboot-env, "a.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
    require-uboot-variable(uboot-env, "a.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
    on-init {
        # Case where A is good, and the desire is to go to B.
        error("It doesn't look like there's anything to revert to in partition B.")
    }
}
task revert.unexpected.b {
    require-uboot-variable(uboot-env, "b.nerves_fw_platform", "${NERVES_FW_PLATFORM}")
    require-uboot-variable(uboot-env, "b.nerves_fw_architecture", "${NERVES_FW_ARCHITECTURE}")
    on-init {
        # Case where B is good, and the desire is to go to A.
        error("It doesn't look like there's anything to revert to in partition A.")
    }
}

task revert.wrongplatform {
    on-init {
        error("Expecting platform=${NERVES_FW_PLATFORM} and architecture=${NERVES_FW_ARCHITECTURE}")
    }
}

# Run "fwup /usr/share/fwup/revert.fw -t status -d /dev/vda -q -U" to check the status.
task status.aa {
    require-path-on-device("/", "/dev/vda2")
    require-uboot-variable(uboot-env, "nerves_fw_active", "a")
    on-init { info("a") }
}
task status.ab {
    require-path-on-device("/", "/dev/vda2")
    require-uboot-variable(uboot-env, "nerves_fw_active", "b")
    on-init { info("a->b") }
}
task status.bb {
    require-path-on-device("/", "/dev/vda3")
    require-uboot-variable(uboot-env, "nerves_fw_active", "b")
    on-init { info("b") }
}
task status.ba {
    require-path-on-device("/", "/dev/vda3")
    require-uboot-variable(uboot-env, "nerves_fw_active", "a")
    on-init { info("b->a") }
}
task status.fail {
    on-init { error("fail") }
}
