From b1ab57d6ada5274fb24e5473cb14a63516741c7d Mon Sep 17 00:00:00 2001 From: Ken Bannister Date: Mon, 13 Jan 2025 10:40:39 -0500 Subject: [PATCH] Use HUP detached mode for improved resilience with an unreliable network Change-type: patch Signed-off-by: Ken Bannister --- src/main.ts | 58 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/main.ts b/src/main.ts index 58e0201..fa40228 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,15 @@ import { getSdk } from 'balena-sdk'; import type { StringValue } from 'ms'; import ms from 'ms'; +/** Functional state of HUP on device for our purposes. */ +enum HupStatus { + RUNNING, + FAILED, + NOT_RUNNING, + /** For example, can't determine status if can't reach API. */ + UNKNOWN, +} + const apiKey = (process.env.BALENA_API_KEY as unknown as string) ?? undefined; const apiUrl = (process.env.BALENA_API_URL as unknown as string) ?? undefined; const deviceUuid = @@ -83,14 +92,26 @@ const getTargetVersion = async ( }); }; -const getUpdateStatus = async (uuid: string): Promise => { +/** Retrieve device model for status of HUP properties. */ +const getUpdateStatus = async (uuid: string): Promise => { try { - const hupStatus = await balena.models.device.getOsUpdateStatus(uuid); - console.log(hupStatus); - return hupStatus; + const hupProps = await balena.models.device.get(uuid, { + $select: ['status', 'provisioning_state', 'provisioning_progress'], + }); + console.log(`Device HUP status: ${JSON.stringify(hupProps)}`); + + if (hupProps.status.toLowerCase() === 'configuring') { + if (hupProps.provisioning_state === 'OS update failed') { + return HupStatus.FAILED; + } else { + return HupStatus.RUNNING; + } + } else { + return HupStatus.NOT_RUNNING; + } } catch (e) { console.error(`Error getting status: ${e}`); - return false; + return HupStatus.UNKNOWN; } }; @@ -105,11 +126,12 @@ const main = async () => { console.log('Checking last update status...'); while ( - await getUpdateStatus(deviceUuid).then((status) => { - return !status || status.status === 'in_progress'; - }) + await getUpdateStatus(deviceUuid).then( + status => status === HupStatus.UNKNOWN + || status === HupStatus.RUNNING, + ) ) { - console.log('Another update is already in progress...'); + console.log('Another update may be in progress...'); await delay('2m'); } @@ -127,16 +149,20 @@ const main = async () => { } else { console.log(`Starting balenaOS host update to ${targetVersion}...`); await balena.models.device - .startOsUpdate(deviceUuid, targetVersion) + .startOsUpdate(deviceUuid, targetVersion, { runDetached: true }) .then(async () => { + // Allow time for server to start HUP on device, which then + // sets Configuring status. + await delay('20s'); while ( - // print progress at regular intervals until status changes - // or device reboots - await getUpdateStatus(deviceUuid).then((status) => { - return !status || status.status === 'in_progress'; - }) + // Print progress at regular intervals while API indicates + // HUP still may be running. + await getUpdateStatus(deviceUuid).then( + status => status === HupStatus.UNKNOWN + || status === HupStatus.RUNNING, + ) ) { - await delay('10s'); + await delay('20s'); } }) .catch((e) => {