From dccf8ac3dca3c651da30bc7369865c100ae68af0 Mon Sep 17 00:00:00 2001 From: Reuben Miller Date: Wed, 29 Nov 2023 15:14:07 +0100 Subject: [PATCH] feat: Add default thin-edge.io firmware support (using dev thin-edge.io version) (#10) * add thin-edge firmware workflow definition and script * add overlay persistence recipe * move tedge config persistence to thin-edge.io recipe * include firmware update recipe in all images * fixup! add overlay persistence recipe * fixup! add overlay persistence recipe * fix permissions on health check folders * add publish image task to build * refactor workflow to use justfile task * refactor build tasks to support profiles and variants * disable manual downloads by default in favor of streaming * improve c8y url detection * fix handling of stream downloads * configure priority given recipe dependency * add logging to thin-edge.io install script (to debug sporadic build issue) --- .github/workflows/bake-image.yml | 21 +- README.md | 42 +- images/pi023.toml | 3 + images/pi23.toml | 26 -- images/pi4.toml | 4 + images/pi45.toml | 24 +- justfile | 55 ++- .../default.toml | 13 +- images/pizero2w.toml => profiles/wifi.toml | 14 +- recipes/persist-overlay/files/ctrl.toml | 1 + recipes/persist-overlay/recipe.toml | 2 + recipes/persist-overlay/steps/00-install.sh | 3 + .../files/tedge-config.toml | 2 - recipes/persist-tedge-config/recipe.toml | 2 - .../persist-tedge-config/steps/00-install.sh | 3 - .../files/health.d/00-time-sync | 46 +++ .../{00-tedge-health => 10-tedge-health} | 0 .../rugpi-auto-rollback/files/healthcheck.sh | 161 ++------ recipes/rugpi-auto-rollback/recipe.toml | 5 +- .../rugpi-auto-rollback/steps/00-install.sh | 6 +- .../files/firmware_update.toml | 53 +++ .../files/rugpi_workflow.sh | 365 ++++++++++++++++++ .../tedge-firmware-update/files/system.toml | 2 + .../files/tedge-firmware | 1 + recipes/tedge-firmware-update/recipe.toml | 4 + .../tedge-firmware-update/steps/00-install.sh | 6 + recipes/thin-edge.io/files/tedge-config.toml | 5 + recipes/thin-edge.io/recipe.toml | 3 +- recipes/thin-edge.io/steps/00-install.sh | 9 +- scripts/upload-c8y.sh | 22 ++ 30 files changed, 641 insertions(+), 262 deletions(-) create mode 100644 images/pi023.toml delete mode 100644 images/pi23.toml create mode 100644 images/pi4.toml rename images/pi4-firmware.toml => profiles/default.toml (69%) rename images/pizero2w.toml => profiles/wifi.toml (72%) create mode 100644 recipes/persist-overlay/files/ctrl.toml create mode 100644 recipes/persist-overlay/recipe.toml create mode 100755 recipes/persist-overlay/steps/00-install.sh delete mode 100644 recipes/persist-tedge-config/files/tedge-config.toml delete mode 100644 recipes/persist-tedge-config/recipe.toml delete mode 100755 recipes/persist-tedge-config/steps/00-install.sh create mode 100755 recipes/rugpi-auto-rollback/files/health.d/00-time-sync rename recipes/rugpi-auto-rollback/files/health.d/{00-tedge-health => 10-tedge-health} (100%) create mode 100644 recipes/tedge-firmware-update/files/firmware_update.toml create mode 100755 recipes/tedge-firmware-update/files/rugpi_workflow.sh create mode 100644 recipes/tedge-firmware-update/files/system.toml create mode 100644 recipes/tedge-firmware-update/files/tedge-firmware create mode 100644 recipes/tedge-firmware-update/recipe.toml create mode 100755 recipes/tedge-firmware-update/steps/00-install.sh create mode 100644 recipes/thin-edge.io/files/tedge-config.toml create mode 100755 scripts/upload-c8y.sh diff --git a/.github/workflows/bake-image.yml b/.github/workflows/bake-image.yml index 9e0e8fb..6590bdc 100644 --- a/.github/workflows/bake-image.yml +++ b/.github/workflows/bake-image.yml @@ -20,26 +20,17 @@ jobs: - uses: extractions/setup-just@v1 - - name: Set image name - run: echo "IMAGE_NAME=$(just generate_version tedge_rugpi_45)" >> $GITHUB_ENV - - name: Install QEMU run: docker run --privileged --rm tonistiigi/binfmt --install arm64 - - name: Set Image - run: just set-image images/pi45.toml - - - name: Extract Image - run: just extract - - - name: Customize System - run: just customize + - name: Set image version + run: echo "VERSION=$(just generate_version)" >> $GITHUB_ENV - - name: Bake Image - run: just bake + - name: Build image + run: just build-all-variants - name: Upload Image uses: actions/upload-artifact@v3 with: - name: ${{ env.IMAGE_NAME }} - path: build/${{ env.IMAGE_NAME }}*.img + name: images + path: build/*.xz diff --git a/README.md b/README.md index 1903d79..9bf9516 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,53 @@ The repository can be used to build custom Raspberry Pi images with thin-edge.io * Raspberry Pi 4 (using tryboot) * Raspberry Pi 5 (using tryboot) -## Building the image +## Building an image To run the build tasks, install [just](https://just.systems/man/en/chapter_5.html). -1. Set which image you want to build +1. Create the image (including downloading the supported base Raspberry Pi image) using: ```sh - just set-image images/pi45.toml + just VARIANT=pi45 build-all ``` -2. Create the image (including downloading the supported base Raspberry Pi image) using: + Possible variants are: + + * pi45 + * pi4 + * pi023 ```sh - just build-all + just PROFILE=wifi VARIANT=pi4 build-all ``` -3. Using the path to the image shown in the console to flash the image to the Raspberry Pi. +2. Using the path to the image shown in the console to flash the image to the Raspberry Pi. For further information, checkout the [Rugpi quick start guide](https://oss.silitics.com/rugpi/docs/getting-started). + +## Building an image with WIFI credentials + +For devices that only support WIFI (e.g. don't have an ethernet adapter), the WIFI credentials are required to be part of the image, otherwise you don't have any way to connect via SSH to your device. + +In the future this process will be looked to be improved, and potentially the standard raspberry pi way of using the wpa_supplicant will enable to work out of the box (so that you don't have to bake credentials into the image, and only add them when writing to flash). + +The default WIFI credentials are as follows, though it assumes that the given WIFI setup is a non-trusted network that is only used for bootstrapping, and then a secure WIFI network is configured. + +|SSID|Password| +|----|--------| +|onboarding_jail|onboarding_jail| + +1. Create the image (including downloading the supported base Raspberry Pi image) using: + + ```sh + just PROFILE=wifi VARIANT=pi023 build-all + ``` + + Possible variants are: + + * pi023 + * pi4 + * pi45 + + This profile will use pre-baked credentials for the WIFI which are defined in [profiles/wifi.toml](profiles/wifi.toml). diff --git a/images/pi023.toml b/images/pi023.toml new file mode 100644 index 0000000..edeb682 --- /dev/null +++ b/images/pi023.toml @@ -0,0 +1,3 @@ +boot_flow = "u-boot" + +include_firmware = "none" diff --git a/images/pi23.toml b/images/pi23.toml deleted file mode 100644 index 00becee..0000000 --- a/images/pi23.toml +++ /dev/null @@ -1,26 +0,0 @@ -recipes = [ - # "set-hostname", - "persist-root-home", - "ssh", - "zsh", -] - -# Make image generic so it can be used for pi4 and pi5 -# So to either pi4 or pi5 if you want to build pi specific images which include the given firmware (EEPROM) -include_firmware = "none" - -boot_flow = "u-boot" - -[parameters.set-hostname] -hostname = "tedge-rugpi" - -[parameters.apt-cleanup] -autoremove = true - -[parameters.ssh] -root_authorized_keys = """ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfhQGWWw73ponAokdNSRZ5cQc9/CIX1TLQgYlr+BtObKoO4UNFP1YSbgK03GjhjeUid+QPmV+UURqxQTqLQoYWqUFP2CYkILFccVPmTvx9HLwupI+6QQKWfMDx9Djfph9GzInymaA5fT7hKppqittFrC/l3lkKgKTX5ohEOGshIbRgtgOYIaW3ByTx3urnaBbYCIgOyOZzSIyS0dUkwsiLu3XjPspgmn3Fs/+vofT/yhBe1carW0UM3ivV0JFfJzrxbCl/F7I2qwfjZXsypjkwlpNupUMuo3xPMi8YvNvyEu4d+IEAqO1dCcdGcxlkiHxrdITIpVLt5mjJ2LauHE/H bootstrap -""" - -[parameters.rugpi-ctrl] -rugpi_admin = true # Enable Rugpi Admin. \ No newline at end of file diff --git a/images/pi4.toml b/images/pi4.toml new file mode 100644 index 0000000..7418bf7 --- /dev/null +++ b/images/pi4.toml @@ -0,0 +1,4 @@ +boot_flow = "tryboot" + +# Include firmware as some older pi4 need a firmware update before tryboot will work +include_firmware = "pi4" diff --git a/images/pi45.toml b/images/pi45.toml index 7b42858..f15ef5d 100644 --- a/images/pi45.toml +++ b/images/pi45.toml @@ -1,26 +1,4 @@ -recipes = [ - # "set-hostname", - "persist-root-home", - "ssh", - "zsh", -] +boot_flow = "tryboot" # Make image generic so it can be used for pi4 and pi5 -# So to either pi4 or pi5 if you want to build pi specific images which include the given firmware (EEPROM) include_firmware = "none" - -boot_flow = "tryboot" - -[parameters.set-hostname] -hostname = "tedge-rugpi" - -[parameters.apt-cleanup] -autoremove = true - -[parameters.ssh] -root_authorized_keys = """ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfhQGWWw73ponAokdNSRZ5cQc9/CIX1TLQgYlr+BtObKoO4UNFP1YSbgK03GjhjeUid+QPmV+UURqxQTqLQoYWqUFP2CYkILFccVPmTvx9HLwupI+6QQKWfMDx9Djfph9GzInymaA5fT7hKppqittFrC/l3lkKgKTX5ohEOGshIbRgtgOYIaW3ByTx3urnaBbYCIgOyOZzSIyS0dUkwsiLu3XjPspgmn3Fs/+vofT/yhBe1carW0UM3ivV0JFfJzrxbCl/F7I2qwfjZXsypjkwlpNupUMuo3xPMi8YvNvyEu4d+IEAqO1dCcdGcxlkiHxrdITIpVLt5mjJ2LauHE/H bootstrap -""" - -[parameters.rugpi-ctrl] -rugpi_admin = true # Enable Rugpi Admin. \ No newline at end of file diff --git a/justfile b/justfile index 4585f95..57a033a 100644 --- a/justfile +++ b/justfile @@ -2,32 +2,42 @@ export IMAGE_URL := "https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-10-10/2023-10-10-raspios-bookworm-arm64-lite.img.xz" export RUGPI_IMAGE := "ghcr.io/silitics/rugpi-bakery:latest" -export IMAGE_NAME := env_var_or_default("IMAGE_NAME", replace_regex(file_stem(IMAGE_URL), ".img$", "")) -export BASE_TAR := "build" / IMAGE_NAME + ".base.tar" -export CUSTOM_TAR := "build" / IMAGE_NAME + ".tedge.tar" -export OUTPUT_IMAGE := "build" / IMAGE_NAME + ".tedge.img" -export BUILD_INFO := file_stem(IMAGE_NAME) +export PREFIX := "tedge_rugpi_" +export PROFILE := "default" + +export BASE_IMAGE := replace_regex(file_stem(IMAGE_URL), ".img$", "") +export BASE_TAR := "build" / BASE_IMAGE + ".base.tar" +export CUSTOM_TAR := "build" / BASE_IMAGE + "." + PROFILE + ".tar" -set-image FILE="images/pi45.toml": - rm -f ./rugpi-bakery.toml - ln -s {{FILE}} ./rugpi-bakery.toml +export CUSTOMIZATION_PROFILE := "profiles" / PROFILE + ".toml" +export VARIANT := "pi45" +export IMAGE_CONFIG := "images/" + VARIANT + ".toml" +export VERSION := env_var_or_default("VERSION", `date +'%Y%m%d%H%M'`) +export IMAGE_NAME := PREFIX + PROFILE + "_" + VARIANT + "_" + VERSION +export OUTPUT_IMAGE := "build" / IMAGE_NAME + ".img" +export BUILD_INFO := file_stem(IMAGE_NAME) # Generate a version name (that can be used in follow up commands) -generate_version prefix="tedge_rugpi": - @echo "{{prefix}}_$(date +'%Y-%m-%d-%H%M')" +generate_version: + @echo "{{VERSION}}" # Show the install paths show: @echo "IMAGE_URL: {{IMAGE_URL}}" @echo "IMAGE_NAME: {{IMAGE_NAME}}" + @echo "CUSTOMIZATION_PROFILE: {{CUSTOMIZATION_PROFILE}}" + @echo "IMAGE_CONFIG: {{IMAGE_CONFIG}}" + @echo "BASE_TAR: {{BASE_TAR}}" @echo "CUSTOM_TAR: {{CUSTOM_TAR}}" + @echo "OUTPUT_IMAGE: {{OUTPUT_IMAGE}}" + @echo "VERSION: {{VERSION}}" @echo "BUILD_INFO: {{BUILD_INFO}}" -# Clean build and cache +# Clean build clean: - @rm -Rf build/ .rugpi/ + @rm -Rf build/ # Download and extract the base image extract: @@ -36,21 +46,25 @@ extract: # Apply recipes to the base image customize: echo "{{BUILD_INFO}}" > "{{justfile_directory()}}/recipes/build-info/files/.build_info" - ./run-bakery customize "{{BASE_TAR}}" "{{CUSTOM_TAR}}" + ./run-bakery --config "{{CUSTOMIZATION_PROFILE}}" customize "{{BASE_TAR}}" "{{CUSTOM_TAR}}" # Create the image that can be flashed to an SD card or applied using the rugpi interface bake: - ./run-bakery bake "{{CUSTOM_TAR}}" "{{OUTPUT_IMAGE}}" + ./run-bakery --config "{{IMAGE_CONFIG}}" bake "{{CUSTOM_TAR}}" "{{OUTPUT_IMAGE}}" + @echo "" + @echo "Compressing image" + xz -0 -v "{{OUTPUT_IMAGE}}" + @echo "" @echo "" @echo "Image created successfully. Check below for options on how to use the image" @echo "" @echo "Option 1: Use the Raspberry Pi Imager to flash the image to an SD card" @echo "" - @echo " {{justfile_directory()}}/{{OUTPUT_IMAGE}}" + @echo " {{justfile_directory()}}/{{OUTPUT_IMAGE}}.xz" @echo "" @echo "Option 2: If the device is already running a rugpi image, open the http://tedge-rugpi:8088 website and install the following image:" @echo "" - @echo " {{justfile_directory()}}/{{OUTPUT_IMAGE}}" + @echo " {{justfile_directory()}}/{{OUTPUT_IMAGE}}.xz" @echo "" # Build the entire image @@ -58,3 +72,12 @@ build-all: extract customize bake # Build the image from an already downloaded image build-local: customize bake + +# Publish latest image to Cumulocity +publish: + cd {{justfile_directory()}} && ./scripts/upload-c8y.sh + +build-all-variants: extract customize + # just VARIANT=pi023 bake + # just VARIANT=pi4 bake + just VARIANT=pi45 bake diff --git a/images/pi4-firmware.toml b/profiles/default.toml similarity index 69% rename from images/pi4-firmware.toml rename to profiles/default.toml index 4f21054..2782949 100644 --- a/images/pi4-firmware.toml +++ b/profiles/default.toml @@ -1,18 +1,11 @@ recipes = [ - # "set-hostname", "persist-root-home", "ssh", "zsh", + "persist-overlay", + "tedge-firmware-update", ] -# Include firmware as some older pi4 need a firmware update before tryboot will work -include_firmware = "pi4" - -boot_flow = "tryboot" - -[parameters.set-hostname] -hostname = "tedge-rugpi" - [parameters.apt-cleanup] autoremove = true @@ -22,4 +15,4 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfhQGWWw73ponAokdNSRZ5cQc9/CIX1TLQgYlr+BtO """ [parameters.rugpi-ctrl] -rugpi_admin = true # Enable Rugpi Admin. \ No newline at end of file +rugpi_admin = true # Enable Rugpi Admin. diff --git a/images/pizero2w.toml b/profiles/wifi.toml similarity index 72% rename from images/pizero2w.toml rename to profiles/wifi.toml index 88ca9c9..c8c7eff 100644 --- a/images/pizero2w.toml +++ b/profiles/wifi.toml @@ -1,22 +1,14 @@ recipes = [ - # "set-hostname", "persist-root-home", "ssh", "zsh", # wifi credentials are required as Raspberry Pi Zero's don't have an ethernet adapter "set-wifi", + "persist-overlay", + "tedge-firmware-update", ] -# Make image generic so it can be used for pi4 and pi5 -# So to either pi4 or pi5 if you want to build pi specific images which include the given firmware (EEPROM) -include_firmware = "none" - -boot_flow = "u-boot" - -[parameters.set-hostname] -hostname = "tedge-rugpi" - [parameters.apt-cleanup] autoremove = true @@ -31,4 +23,4 @@ ssid = "onboarding_jail" password = "onboarding_jail" [parameters.rugpi-ctrl] -rugpi_admin = true # Enable Rugpi Admin. \ No newline at end of file +rugpi_admin = true # Enable Rugpi Admin. diff --git a/recipes/persist-overlay/files/ctrl.toml b/recipes/persist-overlay/files/ctrl.toml new file mode 100644 index 0000000..ce2e88b --- /dev/null +++ b/recipes/persist-overlay/files/ctrl.toml @@ -0,0 +1 @@ +overlay = "persist" \ No newline at end of file diff --git a/recipes/persist-overlay/recipe.toml b/recipes/persist-overlay/recipe.toml new file mode 100644 index 0000000..666e5e9 --- /dev/null +++ b/recipes/persist-overlay/recipe.toml @@ -0,0 +1,2 @@ +description = "Persist general overaly across reboots" +default = false \ No newline at end of file diff --git a/recipes/persist-overlay/steps/00-install.sh b/recipes/persist-overlay/steps/00-install.sh new file mode 100755 index 0000000..f9169ed --- /dev/null +++ b/recipes/persist-overlay/steps/00-install.sh @@ -0,0 +1,3 @@ +#!/bin/sh +set -eu +install -D -m 644 "${RECIPE_DIR}/files/ctrl.toml" -t /etc/rugpi/ diff --git a/recipes/persist-tedge-config/files/tedge-config.toml b/recipes/persist-tedge-config/files/tedge-config.toml deleted file mode 100644 index 847a568..0000000 --- a/recipes/persist-tedge-config/files/tedge-config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[[persist]] -directory = "/etc/tedge" \ No newline at end of file diff --git a/recipes/persist-tedge-config/recipe.toml b/recipes/persist-tedge-config/recipe.toml deleted file mode 100644 index 8109044..0000000 --- a/recipes/persist-tedge-config/recipe.toml +++ /dev/null @@ -1,2 +0,0 @@ -description = "install state file for `/etc/tedge`" -priority = 600_000 # Execute rather early. diff --git a/recipes/persist-tedge-config/steps/00-install.sh b/recipes/persist-tedge-config/steps/00-install.sh deleted file mode 100755 index aec961f..0000000 --- a/recipes/persist-tedge-config/steps/00-install.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -set -eu -install -D -m 644 "${RECIPE_DIR}/files/tedge-config.toml" -t /etc/rugpi/state diff --git a/recipes/rugpi-auto-rollback/files/health.d/00-time-sync b/recipes/rugpi-auto-rollback/files/health.d/00-time-sync new file mode 100755 index 0000000..ed72c22 --- /dev/null +++ b/recipes/rugpi-auto-rollback/files/health.d/00-time-sync @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Wait for network to be ready but don't block if still not available as the mender commit +# might be used to restore network connectivity. +# + +set -e + +OK=0 + +log() { + echo "$*" >&2 +} + +attempt=0 +max_attempts=10 + +# Network ready: 0 = no, 1 = yes +ready=0 +log "Waiting for network to be ready, and time to be synced" + +while [ "$attempt" -lt "$max_attempts" ]; do + TIME_IN_SYNC=$(timedatectl | awk '/System clock synchronized/{print $NF}') + case "${TIME_IN_SYNC}" in + yes) + ready=1 + break + ;; + esac + attempt=$((attempt + 1)) + log "Network not ready yet (attempt: $attempt from $max_attempts)" + sleep 30 +done + +# Duration can only be based on uptime since the device's clock might not be synced yet, so 'date' will not be monotonic +duration=$(awk '{print $1}' /proc/uptime) + +log "Network: ready=$ready (after ${duration}s)" +if [ "$ready" = "1" ]; then + log "Network is ready after ${duration}s (from startup)" +else + # Don't fail, as the downstream checks might still work + log "WARNING: System time is still not in sync but continuing anyway" +fi + +exit ${OK} diff --git a/recipes/rugpi-auto-rollback/files/health.d/00-tedge-health b/recipes/rugpi-auto-rollback/files/health.d/10-tedge-health similarity index 100% rename from recipes/rugpi-auto-rollback/files/health.d/00-tedge-health rename to recipes/rugpi-auto-rollback/files/health.d/10-tedge-health diff --git a/recipes/rugpi-auto-rollback/files/healthcheck.sh b/recipes/rugpi-auto-rollback/files/healthcheck.sh index 7f1b8f7..7da1013 100755 --- a/recipes/rugpi-auto-rollback/files/healthcheck.sh +++ b/recipes/rugpi-auto-rollback/files/healthcheck.sh @@ -1,134 +1,44 @@ #!/bin/sh set -eu -LOG_FILE=/etc/tedge/healthcheck.log - -# Perform simple log rotation, only keep last 200 lines -if [ -f "$LOG_FILE" ]; then - tail -200 "$LOG_FILE" > "${LOG_FILE}.tmp" ||true - mv "${LOG_FILE}.tmp" "$LOG_FILE" ||true -fi - +HEALTH_CHECK_DIR=/etc/health.d _NEWLINE=$(printf '\n') + log() { message="$(date -Iseconds || date --iso-8601=seconds) $*" echo "$message" - # Don't stop if writing to log fails (non critical error) - echo "$message" 2>/dev/null >> "$LOG_FILE" ||true -} - -log_r() { - while IFS=$_NEWLINE read -r line; do - log "$line" - done -} - -HOT=$(/usr/bin/rugpi-ctrl system info | grep Hot | cut -d: -f2 | xargs) -DEFAULT=$(/usr/bin/rugpi-ctrl system info | grep Default | cut -d: -f2 | xargs) -DEVICE_ID="$(tedge config get device.id)" -TARGET="$(tedge config get mqtt.topic_root)/$(tedge config get mqtt.device_topic_id)" -BUILD_INFO=/etc/.build_info - -log "Current rugpi-ctrl state:" -/usr/bin/rugpi-ctrl system info | log_r - -HEALTH_CHECK_DIR=/etc/health.d - -hot_part() { - /usr/bin/rugpi-ctrl system info | grep Hot | cut -d: -f2 | xargs -} - -default_part() { - /usr/bin/rugpi-ctrl system info | grep Default | cut -d: -f2 | xargs -} - -needs_commit() { - [ "$HOT" != "$DEFAULT" ] } is_healthy() { - # 0 = health, 1 = not health (to align with linux exit code convention) - healthy=0 + # 0 = healthy, 1 = not healthy (to align with linux exit code convention) + not_ok=0 if command -V run-parts >/dev/null 2>&1; then log "Using run-parts to execute scripts in $HEALTH_CHECK_DIR" - if ! run-parts --exit-on-error --new-session --lsbsysinit --verbose "$HEALTH_CHECK_DIR" >> "$LOG_FILE" 2>&1; then - healthy=1 + if ! run-parts --exit-on-error --new-session --lsbsysinit --verbose "$HEALTH_CHECK_DIR"; then + not_ok=1 fi else - log "Using find to execute scripts in $HEALTH_CHECK_DIR" - find "$HEALTH_CHECK_DIR" -prune -type f -mode 0755 -exec {} \; >> "$LOG_FILE" 2>&1 + log "Using for loop to execute scripts in $HEALTH_CHECK_DIR" + for file in "$HEALTH_CHECK_DIR"/*; do + if [ -x "$file" ]; then + if ! "$file"; then + not_ok=1 + break + fi + fi + done fi - return "$healthy" -} - -collect_rugpi() { - if [ -z "$DEVICE_ID" ]; then - return 0 - fi - - # Collect firmware information - firmware_name= - firmware_version= - if [ -f "$BUILD_INFO" ]; then - firmware_name=$(cut -d_ -f1-2 "$BUILD_INFO") - firmware_version=$(cut -d_ -f3- "$BUILD_INFO") - fi - if [ -z "$firmware_name" ]; then - firmware_name=tedge_rugpi - fi - if [ -z "$firmware_version" ]; then - firmware_version=unknown - fi - PAYLOAD=$(printf '{"name":"%s","version":"%s"}' "$firmware_name" "$firmware_version") - tedge mqtt pub -q 1 -r "$TARGET/twin/c8y_Firmware" "$PAYLOAD" ||: - - # Collect rugpi state information, e.g. which partition is active - PAYLOAD=$(printf '{"hot":"%s","default":"%s"}' "$HOT" "$DEFAULT") - tedge mqtt pub -q 1 -r "$TARGET/twin/rugpi" "$PAYLOAD" ||: - - # publish event for chronological order - PAYLOAD=$(printf '{"text":"Partition info. hot=%s (name=%s, version=%s), default=%s"}' "$HOT" "$firmware_name" "$firmware_version" "$DEFAULT") - tedge mqtt pub -q 1 "$TARGET/e/device_boot" "$PAYLOAD" ||: -} - -try_wait_for_broker() { - # Try to wait until the broker is ready before publishing data. - # Proceed anyway if it still is not ready have N tries - RETRIES=10 - while [ "$RETRIES" -gt 0 ]; do - if tedge mqtt pub 'dummy/message' ''; then - log "Broker is ready" - return 0 - fi - RETRIES=$((RETRIES - 1)) - sleep 5 - done - - log "Broker is not ready, but continuing anyway" -} - -publish_system_info() { - try_wait_for_broker - collect_rugpi + return "$not_ok" } main() { counter=0 - COMMIT=0 - - # Wait for broker before checking health, but don't block the health check - try_wait_for_broker - - PAYLOAD=$(printf '{"text":"Booted into new image. Checking health before committing. hot=%s, default=%s"}' "$HOT" "$DEFAULT") - tedge mqtt pub -q 1 "$TARGET/e/image_check" "$PAYLOAD" ||: - - # DEBUG: Give a chance to manually intercept this - log "Waiting 10 minutes before checking health:" - sleep 600 + NOT_OK=1 + RETRY_DELAY=30 while [ "$counter" -lt 10 ]; do if is_healthy; then - COMMIT=1 + NOT_OK=0 break fi @@ -137,38 +47,11 @@ main() { else counter=$((counter + 1)) fi - log "Waiting 60 seconds before checking the health again" - sleep 60 + log "Waiting $RETRY_DELAY seconds before checking the health again" + sleep "$RETRY_DELAY" done - if [ "$COMMIT" = "0" ]; then - log "Switching back to default partition: $DEFAULT" - PAYLOAD=$(printf '{"text":"Health check failed. Rolling back to default partition. hot=%s, default=%s"}' "$HOT" "$DEFAULT") - tedge mqtt pub -q 1 "$TARGET/e/image_rollback" "$PAYLOAD" ||: - - /usr/bin/rugpi-ctrl system reboot - exit 0 - fi - - log "Making Hot partition the default partition: $DEFAULT" - /usr/bin/rugpi-ctrl system commit - log "Committed succesfully" - - # Refresh hot/default partition info as they change after a commit - HOT=$(hot_part) - DEFAULT=$(default_part) - - # Send notification that the image was committed - PAYLOAD=$(printf '{"text":"Health check passed. Changing default partition. hot=%s, default=%s"}' "$HOT" "$DEFAULT") - tedge mqtt pub "$TARGET/e/image_commit" "$PAYLOAD" ||: - - publish_system_info + exit "$NOT_OK" } -if ! needs_commit; then - log "Already on default partition. No commit/rollback needed. hot=$HOT, default=$DEFAULT" - publish_system_info - exit 0 -fi - main diff --git a/recipes/rugpi-auto-rollback/recipe.toml b/recipes/rugpi-auto-rollback/recipe.toml index 0d65c1a..2a4f4d8 100644 --- a/recipes/rugpi-auto-rollback/recipe.toml +++ b/recipes/rugpi-auto-rollback/recipe.toml @@ -1,2 +1,5 @@ description = "enabling Rugpi Ctrl auto rollback" -priority = 80_000 \ No newline at end of file +priority = 80_000 + +[parameters] +enabled = { default = false } diff --git a/recipes/rugpi-auto-rollback/steps/00-install.sh b/recipes/rugpi-auto-rollback/steps/00-install.sh index d010354..6218ad3 100755 --- a/recipes/rugpi-auto-rollback/steps/00-install.sh +++ b/recipes/rugpi-auto-rollback/steps/00-install.sh @@ -2,10 +2,12 @@ set -eu mkdir -p /etc/health.d -chmod 644 /etc/health.d +chmod 755 /etc/health.d install -D -m 755 "${RECIPE_DIR}/files/health.d/"* -t /etc/health.d/ install -D -m 755 "${RECIPE_DIR}/files/healthcheck.sh" -t /usr/bin/ install -D -m 644 "${RECIPE_DIR}/files/rugpi-auto-rollback.service" -t /usr/lib/systemd/system/ -systemctl enable rugpi-auto-rollback +if [ "${RECIPE_PARAM_ENABLED}" = "true" ]; then + systemctl enable rugpi-auto-rollback +fi diff --git a/recipes/tedge-firmware-update/files/firmware_update.toml b/recipes/tedge-firmware-update/files/firmware_update.toml new file mode 100644 index 0000000..39d0493 --- /dev/null +++ b/recipes/tedge-firmware-update/files/firmware_update.toml @@ -0,0 +1,53 @@ +operation = "firmware_update" + +[init] +next = ["scheduled"] + +[scheduled] +next = ["executing"] + +[executing] +script = "/usr/bin/rugpi_workflow.sh executing --id ${.topic.cmd_id} --on-success download" +next = ["download"] + +[download] +script = "/usr/bin/rugpi_workflow.sh download --id ${.topic.cmd_id} --url ${.payload.remoteUrl} --on-success install --on-error failed" +next = ["install"] + +[install] +script = "/usr/bin/rugpi_workflow.sh install --id ${.topic.cmd_id} --url ${.payload.url} --on-success restart --on-restart restart --on-error failed" +next = ["commit", "restart", "failed"] + +[restart] +script = "restart" +next = ["restarting", "restarted", "failed_restart"] + +[restarted] +script = "/usr/bin/rugpi_workflow.sh restarted --id ${.topic.cmd_id} --on-success verify" +next = ["verify"] + +[failed_restart] +script = "/usr/bin/rugpi_workflow.sh failed_restart --id ${.topic.cmd_id} --on-success failed --on-error failed" +next = ["failed"] + +[verify] +script = "/usr/bin/rugpi_workflow.sh verify --id ${.topic.cmd_id} --firmware-name ${.payload.name} --firmware-version ${.payload.version} --url ${.payload.remoteUrl} --on-success commit --on-restart rollback_restart --on-error failed" +next = ["commit", "rollback_restart", "failed"] + +[commit] +script = "/usr/bin/rugpi_workflow.sh commit --id ${.topic.cmd_id} --firmware-name ${.payload.name} --firmware-version ${.payload.version} --url ${.payload.remoteUrl} --on-success successful --on-restart rollback_restart --on-error failed" +next = ["successful", "rollback_restart"] + +[rollback_restart] +script = "restart" +next = ["restarting", "rollback_successful", "failed"] + +[rollback_successful] +script = "/usr/bin/rugpi_workflow.sh rollback_successful --id ${.topic.cmd_id} --on-success failed" +next = ["failed"] + +[successful] +next = [] + +[failed] +next = [] diff --git a/recipes/tedge-firmware-update/files/rugpi_workflow.sh b/recipes/tedge-firmware-update/files/rugpi_workflow.sh new file mode 100755 index 0000000..9080de1 --- /dev/null +++ b/recipes/tedge-firmware-update/files/rugpi_workflow.sh @@ -0,0 +1,365 @@ +#!/bin/sh +set -e +ON_SUCCESS="successful" +ON_ERROR="failed" +ON_RESTART="restart" +FIRMWARE_NAME= +FIRMWARE_VERSION= +FIRMWARE_URL= +CMD_ID=x +FIRMWARE_META_FILE=/etc/tedge/.firmware +LOG_FILE=/etc/tedge/firmware_update.log +MANUAL_DOWNLOAD=0 + +# Use temp directory so that the file can't accidentally persist across partitions +# thus always booting into the spare partition +REBOOT_SPARE_REQUEST=/tmp/.reboot_spare + +SUDO="sudo" + +_WORKDIR=$(pwd) + +# Change to a directory which is readable otherwise rugpi-ctrl can have problems reading the mounts +cd / + +HOT=$(rugpi-ctrl system info | grep Hot | cut -d: -f2 | xargs) +DEFAULT=$(rugpi-ctrl system info | grep Default | cut -d: -f2 | xargs) + +ACTION="$1" +shift + + +log() { + msg="$(date +%Y-%m-%dT%H:%M:%S) [cmd=$CMD_ID, current=$ACTION] $*" + echo "$msg" >&2 + + # publish to pub for better resolution + current_partition=$(get_current_partition) + tedge mqtt pub -q 2 te/device/main///e/firmware_update "{\"text\":\"Firmware Workflow: [$ACTION] $*\",\"command_id\":\"$CMD_ID\",\"state\":\"$ACTION\",\"partition\":\"$current_partition\"}" + sleep 1 + + if [ -n "$LOG_FILE" ]; then + echo "$msg" >> "$LOG_FILE" + fi +} + +local_log() { + # Only log locally and don't push to the cloud + msg="$(date +%Y-%m-%dT%H:%M:%S) [cmd=$CMD_ID, current=$ACTION] $*" + echo "$msg" >&2 + + if [ -n "$LOG_FILE" ]; then + echo "$msg" >> "$LOG_FILE" + fi +} + +next_state() { + status="$1" + reason= + + if [ $# -gt 1 ]; then + reason="$2" + fi + + if [ -n "$reason" ]; then + log "Moving to next State: $status. reason=$reason" + printf '{"status":"%s","reason":"%s"}\n' "$status" "$reason" + else + log "Moving to next State: $status" + printf '{"status":"%s"}\n' "$status" + fi + sleep 1 +} + +# +# main +# +while [ $# -gt 0 ]; do + case "$1" in + --id) + CMD_ID="$2" + shift + ;; + --firmware-name) + FIRMWARE_NAME="$2" + shift + ;; + --firmware-version) + FIRMWARE_VERSION="$2" + shift + ;; + --on-success) + ON_SUCCESS="$2" + shift + ;; + --on-error) + ON_ERROR="$2" + shift + ;; + --on-restart) + ON_RESTART="$2" + shift + ;; + --url) + FIRMWARE_URL="$2" + shift + ;; + esac + shift +done + +wait_for_network() { + # + # Wait for network to be ready but don't block if still not available as the commit + # might be used to restore network connectivity. + # + attempt=0 + max_attempts=10 + # Network ready: 0 = no, 1 = yes + ready=0 + local_log "Waiting for network to be ready, and time to be synced" + + while [ "$attempt" -lt "$max_attempts" ]; do + # TIME_SYNC_ACTIVE=$(timedatectl | grep NTP | awk '{print $NF}') + TIME_IN_SYNC=$(timedatectl | awk '/System clock synchronized/{print $NF}') + case "${TIME_IN_SYNC}" in + yes) + ready=1 + break + ;; + esac + attempt=$((attempt + 1)) + local_log "Network not ready yet (attempt: $attempt from $max_attempts)" + sleep 30 + done + + # Duration can only be based on uptime since the device's clock might not be synced yet, so 'date' will not be monotonic + duration=$(awk '{print $1}' /proc/uptime) + + local_log "Network: ready=$ready (after ${duration}s)" + if [ "$ready" = "1" ]; then + log "Network is ready after ${duration}s (from startup)" + return 0 + fi + + # Don't send cloud message if it is not ready + return 1 +} + +get_current_partition() { + current=$(rugpi-ctrl system info | grep Hot | cut -d: -f2 | xargs | tr '[:lower:]' '[:upper:]') + echo "$current" +} + +get_next_partition() { + [ "$1" = "A" ] && echo "B" || echo "A" +} + +executing() { + current_partition=$(get_current_partition) + next_partition=$(get_next_partition "$current_partition") + { + echo "---------------------------------------------------------------------------" + echo "Firmware update (id=$CMD_ID): $current_partition -> $next_partition" + echo "---------------------------------------------------------------------------" + } >> "$LOG_FILE" + log "Starting firmware update. Current partition is $(get_current_partition), so update will be applied to $next_partition" +} + +download() { + status="$1" + url="$2" + + # + # Change url to a local url using the c8y proxy + # + case "$url" in + https://*/inventory/binaries/*) + # Cumulocity URL, use the c8y auth proxy service + partial_path=$(echo "$url" | sed 's|https://[^/]*/||g') + c8y_proxy_host=$(tedge config get c8y.proxy.client.host) + c8y_proxy_port=$(tedge config get c8y.proxy.client.port) + tedge_url="http://${c8y_proxy_host}:${c8y_proxy_port}/c8y/$partial_path" + ;; + http://*|https://*) + # External URL, pass it untouched + # NOTE: If a service required authorization, then this would be the place to add it + # For example some blob stores support signed URLS + partial_path=$(echo "$url" | sed 's|https://[^/]*/||g') + tedge_url="$url" + ;; + *) + # Assume url is actually a file and just go to the next state + printf '{"status":"%s","url":"%s"}\n' "$status" "$url" + return 0 + ;; + esac + + TEDGE_DATA=$(tedge config get data.path) + + if [ "$MANUAL_DOWNLOAD" = 1 ]; then + + # Removing any older files to ensure space for next file to download + # Note: busy box does not support -delete + find "$TEDGE_DATA" -name "*.firmware" -exec rm {} \; + + last_part=$(echo "$partial_path" | rev | cut -d/ -f1 | rev) + local_file="$TEDGE_DATA/${last_part}.firmware" + log "Manually downloading artifact from $tedge_url and saving to $local_file" + wget -c -O "$local_file" "$tedge_url" >&2 + log "Downloaded file from: $tedge_url" + printf '{"status":"%s","url":"%s"}\n' "$status" "$local_file" + else + log "Converted to local url: $url => $tedge_url" + printf '{"status":"%s","url":"%s"}\n' "$status" "$tedge_url" + fi +} + +install() { + url="$1" + set +e + case "$url" in + http://*.img|https://*.img) + log "Executing: wget -c -q -t 0 -O - '$url' | $SUDO rugpi-ctrl update install --stream --no-reboot -" + wget -c -q -t 0 -O - "$url" | $SUDO rugpi-ctrl update install --stream --no-reboot - >>"$LOG_FILE" 2>&1 + ;; + + # Assume a xz compressed file + http://*|https://*) + log "Executing: wget -c -q -t 0 -O - '$url' | xz -d | $SUDO rugpi-ctrl update install --stream --no-reboot -" + wget -c -q -t 0 -O - "$url" | xz -d | $SUDO rugpi-ctrl update install --stream --no-reboot - >>"$LOG_FILE" 2>&1 + ;; + + # It is a file + *) + # Check file type using mime types + mime_type=$(file "$url" --mime-type | cut -d: -f2 | xargs) + + case "$mime_type" in + application/x-xz) + # Decode the file and stream it into rugpi (decompressing on the fly) + log "Executing: xz -d -T0 -c '$url' | $SUDO rugpi-ctrl update install --stream --no-reboot -" + xz --decompress --stdout -T0 "$url" | $SUDO rugpi-ctrl update install --stream --no-reboot - >>"$LOG_FILE" 2>&1 + ;; + *) + # Uncompressed file + log "Executing: rugpi-ctrl update install --no-reboot '$url'" + $SUDO rugpi-ctrl update install --no-reboot "$url" >>"$LOG_FILE" 2>&1 + ;; + esac + ;; + esac + EXIT_CODE=$? + set -e + + case "$EXIT_CODE" in + 0) + log "OK, RESTART required" + next_state "$ON_RESTART" + ;; + *) + log "ERROR. Unexpected return code. code=$EXIT_CODE" + next_state "$ON_ERROR" "ERROR. Unexpected return code. code=$EXIT_CODE" + ;; + esac + + # Create mark file which is used by the restart state to reboot into the spare partition + touch "$REBOOT_SPARE_REQUEST" +} + +restart() { + # NOTE: This function should not be called in the script directly but rather via the system.toml + current_partition=$(get_current_partition) + next_partition=$(get_next_partition "$current_partition") + + if [ -f "$REBOOT_SPARE_REQUEST" ]; then + rm -f "$REBOOT_SPARE_REQUEST" + + message=$(printf '{"text":"Rebooting into spare partition (%s -> %s)","partition":"%s"}' "$current_partition" "$next_partition" "$current_partition") + tedge mqtt pub -q 1 "te/device/main///e/reboot_spare" "$message" ||: + sleep 5 + $SUDO rugpi-ctrl system reboot --spare + else + message=$(printf '{"text":"Rebooting into default partition (%s -> %s)","partition":"%s"}' "$current_partition" "$DEFAULT" "$current_partition") + tedge mqtt pub -q 1 "te/device/main///e/reboot_default" "$message" ||: + sleep 5 + $SUDO rugpi-ctrl system reboot + fi + exit 0 +} + +verify() { + log "Checking device health" + + if [ "$HOT" = "$DEFAULT" ]; then + # Don't both to reboot if no partition swap occurred because we are already in the ok partition + next_state "$ON_ERROR" "Partition swap did not occur. Reasons could be, corrupt/non-bootable image, someone did a manual rollback or the machine was restarted manually before the health check was run" + return + fi + + # Allow users to also call addition logic by adding their scripts to the /etc/health.d/ directory + if /usr/bin/healthcheck.sh >> "$LOG_FILE" 2>&1; then + next_state "$ON_SUCCESS" + else + log "Health check failed on new partition" + next_state "$ON_RESTART" + fi +} + +commit() { + log "Executing: rugpi-ctrl system commit" + set +e + $SUDO rugpi-ctrl system commit >> "$LOG_FILE" 2>&1 + EXIT_CODE=$? + set -e + + case "$EXIT_CODE" in + 0) + log "Commit successful. New default partition is $(get_current_partition)" + + # Save firmware meta information to file (for reading on startup during normal operation) + local_log "Saving firmware info to $FIRMWARE_META_FILE" + printf 'FIRMWARE_NAME=%s\nFIRMWARE_VERSION=%s\nFIRMWARE_URL=%s\n' "$FIRMWARE_NAME" "$FIRMWARE_VERSION" "$FIRMWARE_URL" > "$FIRMWARE_META_FILE" + + next_state "$ON_SUCCESS" + ;; + *) + log "rugpi-ctrl returned code: $EXIT_CODE. Rolling back to previous partition" + next_state "$ON_RESTART" + ;; + esac +} + +case "$ACTION" in + executing) + executing + next_state "$ON_SUCCESS" + ;; + download) download "$ON_SUCCESS" "$FIRMWARE_URL"; ;; + install) install "$FIRMWARE_URL"; ;; + verify) verify; ;; + commit) commit; ;; + restart) restart; ;; + restarted) + wait_for_network ||: + log "Device has been restarted...continuing workflow. partition=$(get_current_partition)" + next_state "$ON_SUCCESS" + ;; + rollback_successful) + next_state "$ON_SUCCESS" "Firmware update failed, but the rollback was successful. partition=$(get_current_partition)" + ;; + failed_restart) + # There is no success/failed action here, we always transition to the next state + # Only an error reason is added + next_state "$ON_SUCCESS" "Device failed to restart" + ;; + *) + log "Unknown command. This script only accepts: download, install, commit, rollback, rollback_successful, failed_restart" + exit 1 + ;; +esac + +# switch back to original directory +cd "$_WORKDIR" ||: + +exit 0 diff --git a/recipes/tedge-firmware-update/files/system.toml b/recipes/tedge-firmware-update/files/system.toml new file mode 100644 index 0000000..f87cea3 --- /dev/null +++ b/recipes/tedge-firmware-update/files/system.toml @@ -0,0 +1,2 @@ +[system] +reboot = ["/usr/bin/rugpi_workflow.sh", "restart"] \ No newline at end of file diff --git a/recipes/tedge-firmware-update/files/tedge-firmware b/recipes/tedge-firmware-update/files/tedge-firmware new file mode 100644 index 0000000..4e4e994 --- /dev/null +++ b/recipes/tedge-firmware-update/files/tedge-firmware @@ -0,0 +1 @@ +tedge ALL = (ALL) NOPASSWD: /usr/bin/rugpi-ctrl, /usr/bin/rugpi_workflow.sh \ No newline at end of file diff --git a/recipes/tedge-firmware-update/recipe.toml b/recipes/tedge-firmware-update/recipe.toml new file mode 100644 index 0000000..abaa953 --- /dev/null +++ b/recipes/tedge-firmware-update/recipe.toml @@ -0,0 +1,4 @@ +description = "Add workflow scripts to support firmware updates" +default = false +priority = 10_000 +dependencies = ["thin-edge.io"] \ No newline at end of file diff --git a/recipes/tedge-firmware-update/steps/00-install.sh b/recipes/tedge-firmware-update/steps/00-install.sh new file mode 100755 index 0000000..1e61b88 --- /dev/null +++ b/recipes/tedge-firmware-update/steps/00-install.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +install -D -m 644 "${RECIPE_DIR}/files/tedge-firmware" -t /etc/sudoers.d/ +install -D -m 644 "${RECIPE_DIR}/files/system.toml" -t /etc/tedge/ +install -D -m 644 "${RECIPE_DIR}/files/firmware_update.toml" -t /etc/tedge/operations/ +install -D -m 755 "${RECIPE_DIR}/files/rugpi_workflow.sh" -t /usr/bin/ diff --git a/recipes/thin-edge.io/files/tedge-config.toml b/recipes/thin-edge.io/files/tedge-config.toml new file mode 100644 index 0000000..06d95c3 --- /dev/null +++ b/recipes/thin-edge.io/files/tedge-config.toml @@ -0,0 +1,5 @@ +[[persist]] +directory = "/etc/tedge" + +[[persist]] +directory = "/etc/mosquitto" diff --git a/recipes/thin-edge.io/recipe.toml b/recipes/thin-edge.io/recipe.toml index 9325019..563523d 100644 --- a/recipes/thin-edge.io/recipe.toml +++ b/recipes/thin-edge.io/recipe.toml @@ -1 +1,2 @@ -description = "install thin-edge.io" \ No newline at end of file +description = "install thin-edge.io" +priority = 20_000 \ No newline at end of file diff --git a/recipes/thin-edge.io/steps/00-install.sh b/recipes/thin-edge.io/steps/00-install.sh index 6d9bab4..3c66f06 100755 --- a/recipes/thin-edge.io/steps/00-install.sh +++ b/recipes/thin-edge.io/steps/00-install.sh @@ -1,7 +1,7 @@ #!/bin/bash -e # install thin-edge.io -curl -fsSL https://thin-edge.io/install.sh | sh -s -- --channel main +curl -fsSL https://thin-edge.io/install.sh | sh -s -- --channel dev | tee -a "${RECIPE_DIR}/build.log" # Install collectd apt-get install -y -o DPkg::Options::=--force-confnew --no-install-recommends \ @@ -9,7 +9,7 @@ apt-get install -y -o DPkg::Options::=--force-confnew --no-install-recommends \ c8y-command-plugin \ tedge-collectd-setup \ tedge-monit-setup \ - tedge-inventory-plugin + tedge-inventory-plugin | tee -a "${RECIPE_DIR}/build.log" # custom tedge configuration tedge config set apt.name "(tedge|c8y|python|wget|vim|curl|apt|mosquitto|ssh|sudo).*" @@ -27,6 +27,5 @@ systemctl enable collectd # Custom mosquitto configuration install -D -m 644 "${RECIPE_DIR}/files/custom.conf" -t /etc/tedge/mosquitto-conf/ -# TODO: should overlay be persisted by default, otherwise someone can accidentally disable a service -# and leave it off, however otherwise it is a bit harder to control services during runtime -#rugpi-ctrl state overlay set-persist true +# Persist tedge configuration and related components (e.g. mosquitto) +install -D -m 644 "${RECIPE_DIR}/files/tedge-config.toml" -t /etc/rugpi/state diff --git a/scripts/upload-c8y.sh b/scripts/upload-c8y.sh new file mode 100755 index 0000000..484238c --- /dev/null +++ b/scripts/upload-c8y.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -e +LATEST= +FIRMWARE=tedge_rugpi_45 + +if [ $# -gt 0 ]; then + LATEST="$1" +fi + +unset -v LATEST +for file in "build"/*; do + [[ "$file" -nt "$LATEST" ]] && LATEST=$file +done + +VERSION=$(echo "$LATEST" | sed 's/.*_//g' | sed 's/.img.xz//g') + +if [ -z "$VERSION" ]; then + echo "Could not detect version" + exit +fi + +c8y firmware versions create --firmware "$FIRMWARE" --file "$LATEST" --version "$VERSION"