/** * OTA Module * Features: * - HTTP online and local file upgrades * - Automatic MD5 integrity verification * - Pre-upgrade disk space validation * Usage: 1. Build code into an app package. Click `Package` in VSCode DejaOS Plugin to generate a .dpk file in .temp folder. 2. Upload the .dpk file (zip format) to a web server and get the download URL. 3. Send the download URL and MD5 checksum to the device app. - Encode URL and MD5 as QR code for device scanning, - Or use other methods (Bluetooth, MQTT, RS485, etc.). 4. Device downloads and verifies package integrity using MD5. 5. Extract package to stable directory and reboot device. 6. After reboot, DejaOS extracts package and overwrites existing code. * Doc/Demo: https://github.com/DejaOS/DejaOS */ import log from './dxLogger.js' import com from './dxCommon.js' import http from './dxHttpClient.js' import * as os from 'os'; const ota = {} ota.UPGRADE_TARGET = '/upgrades.zip' ota.UPGRADE_TEMP = '/upgrades.temp' ota.DF_CMD = `df -k / | awk 'NR==2 {print $4}'` /** * Download upgrade package via HTTP * @param {string} url Required. HTTP URL for downloading the upgrade package * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex) * @param {number} timeout Optional. Download timeout in seconds (default: 60) * @param {number} size Optional. Package size in KB for disk space validation * @param {Object} [httpOpts] Additional request opts */ ota.updateHttp = function (url, md5, timeout = 60, size, httpOpts) { if (!url || !md5) { throw new Error("'url' and 'md5' parameters are required") } if (size && (typeof size != 'number')) { throw new Error("'size' parameter must be a number") } // 1. Check available disk space checkDiskSpace(size) // 2. Download file to temporary directory com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `) // Clean up existing files log.info("download url:" + url) let downloadRet = http.download(url, ota.UPGRADE_TEMP, timeout * 1000, httpOpts) log.info("download result:" + JSON.stringify(downloadRet)) let fileExist = (os.stat(ota.UPGRADE_TEMP)[1] === 0) if (!fileExist) { com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `) throw new Error('Download failed. Please check the URL: ' + url) } // 3. Verify MD5 checksum if (!verifyMD5(ota.UPGRADE_TEMP, md5)) { com.systemBrief(`rm -rf ${ota.UPGRADE_TARGET} && rm -rf ${ota.UPGRADE_TEMP} `) throw new Error('MD5 verification failed') } // 4. Move verified package to upgrade directory com.systemBrief(`mv ${ota.UPGRADE_TEMP} ${ota.UPGRADE_TARGET} `) com.systemBrief(`sync`) } /** * Upgrade from local file * Use this when you've already downloaded the package via custom methods. * @param {string} path Required. Path to the upgrade package * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex) * @param {number} size Optional. Package size in KB for disk space validation */ ota.updateFile = function (path, md5, size) { if (!path || !md5) { throw new Error("'path' and 'md5' parameters are required") } if (size && (typeof size != 'number')) { throw new Error("'size' parameter must be a number") } let fileExist = (os.stat(path)[1] === 0) if (!fileExist) { throw new Error('File not found: ' + path) } // 1. Check available disk space checkDiskSpace(size) // 2. Verify MD5 checksum if (!verifyMD5(path, md5)) { throw new Error('MD5 verification failed') } // 3. Move package to upgrade directory com.systemBrief(`mv ${path} ${ota.UPGRADE_TARGET} `) com.systemBrief(`sync`) } function checkDiskSpace(requiredKb) { if (requiredKb) { const df = parseInt(com.systemWithRes(ota.DF_CMD, 1000)) if (df < 3 * requiredKb) { throw new Error('Insufficient disk space for upgrade') } } } function verifyMD5(filePath, expectedMD5) { const hash = com.md5HashFile(filePath) const actualMD5 = hash.map(v => v.toString(16).padStart(2, '0')).join('') return actualMD5 === expectedMD5 } /** * Trigger device reboot * Call this after successful upgrade to apply changes. */ ota.reboot = function () { com.asyncReboot(2) } //-------------------------DEPRECATED------------------- ota.OTA_ROOT = '/ota' ota.OTA_RUN = ota.OTA_ROOT + '/run.sh' /** * @deprecated Use updateHttp() instead * Legacy upgrade method with custom script support. * Downloads, extracts, and executes custom upgrade scripts. * @param {string} url Required. HTTP URL for downloading the upgrade package * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex) * @param {number} size Optional. Package size in KB for disk space validation * @param {string} shell Optional. Custom upgrade script content * @param {number} timeout Optional. Connection timeout in seconds (default: 3) */ ota.update = function (url, md5, size, shell, timeout = 3) { if (!url || !md5) { throw new Error("'url' and 'md5' parameters are required") } if (size && (typeof size != 'number')) { throw new Error("'size' parameter must be a number") } // 1. Check available disk space let df = parseInt(com.systemWithRes(ota.DF_CMD, 1000)) if (size) { if (df < (3 * size)) { // Require 3x package size for extraction throw new Error('Insufficient disk space for upgrade') } } // 2. Download to specific directory const firmware = ota.OTA_ROOT + '/download.zip' const temp = ota.OTA_ROOT + '/temp' com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // Clean and create directory let download = `wget --no-check-certificate --timeout=${timeout} -c "${url}" -O ${firmware} 2>&1` com.systemBrief(download, 1000) let fileExist = (os.stat(firmware)[1] === 0) let downloadRet if (!fileExist) { downloadRet = http.download(url, firmware, timeout * 1000) } fileExist = (os.stat(firmware)[1] === 0) if (!fileExist) { log.error("download result" + downloadRet) throw new Error('Download failed. Please check the URL: ' + url) } // 3. Verify MD5 checksum let md5Hash = com.md5HashFile(firmware) md5Hash = md5Hash.map(v => v.toString(16).padStart(2, 0)).join('') if (md5Hash != md5) { log.error("download result" + downloadRet) throw new Error('MD5 verification failed') } // 4. Extract package com.systemBrief(`mkdir ${temp} && unzip -o ${firmware} -d ${temp}`) // 5. Execute custom upgrade script if present const custom_update = temp + '/custom_update.sh' if (os.stat(custom_update)[1] === 0) { com.systemBrief(`chmod +x ${custom_update}`) com.systemWithRes(`${custom_update}`) } // 6. Create upgrade script if (!shell) { // Default: copy files and clean up shell = `cp -r ${temp}/* /app/code \n rm -rf ${ota.OTA_ROOT}` } com.systemBrief(`echo "${shell}" > ${ota.OTA_RUN} && chmod +x ${ota.OTA_RUN}`) fileExist = (os.stat(ota.OTA_RUN)[1] === 0) if (!fileExist) { throw new Error('Failed to create upgrade script') } com.systemWithRes(`${ota.OTA_RUN}`) } /** * @deprecated Use updateHttp() instead * Legacy resource upgrade for tar.xz packages. * Specialized for upgrading resource files only. * @param {string} url Required. HTTP URL for downloading the upgrade package * @param {string} md5 Required. MD5 hash for integrity verification (32-char lowercase hex) * @param {number} size Optional. Package size in KB for disk space validation * @param {string} shell Optional. Custom upgrade script content * @param {number} timeout Optional. Connection timeout in seconds (default: 3) */ ota.updateResource = function (url, md5, size, shell, timeout = 3) { if (!url || !md5) { throw new Error("'url' and 'md5' parameters are required") } if (size && (typeof size != 'number')) { throw new Error("'size' parameter must be a number") } // 1. Check available disk space let df = parseInt(com.systemWithRes(ota.DF_CMD, 1000)) if (size) { if (df < (3 * size)) { // Require 3x package size for extraction throw new Error('Insufficient disk space for upgrade') } } // 2. Download to specific directory const firmware = ota.OTA_ROOT + '/download.tar.xz' const temp = ota.OTA_ROOT + '/temp' com.systemBrief(`rm -rf ${ota.OTA_ROOT} && mkdir ${ota.OTA_ROOT} `) // Clean and create directory let download = `wget --no-check-certificate --timeout=${timeout} -c "${url}" -O ${firmware} 2>&1` com.systemBrief(download, 1000) let fileExist = (os.stat(firmware)[1] === 0) if (!fileExist) { http.download(url, firmware, timeout * 1000) } fileExist = (os.stat(firmware)[1] === 0) if (!fileExist) { throw new Error('Download failed. Please check the URL: ' + url) } // 3. Verify MD5 checksum let md5Hash = com.md5HashFile(firmware) md5Hash = md5Hash.map(v => v.toString(16).padStart(2, 0)).join('') if (md5Hash != md5) { throw new Error('MD5 verification failed') } // 4. Extract tar.xz package com.systemBrief(`mkdir ${temp} && tar -xJvf ${firmware} -C ${temp}`) // 5. Create resource upgrade script if (!shell) { shell = ` source=${temp}/vgapp/res/image/bk.png target=/app/code/resource/image/bg.png if test -e "\\$source"; then cp "\\$source" "\\$target" fi source=${temp}/vgapp/res/image/bk_90.png target=/app/code/resource/image/bg_90.png if test -e "\\$source"; then cp "\\$source" "\\$target" fi source=${temp}/vgapp/res/font/AlibabaPuHuiTi-2-65-Medium.ttf target=/app/code/resource/font.ttf if test -e "\\$source"; then cp "\\$source" "\\$target" fi source=${temp}/vgapp/wav/*.wav target=/app/code/resource/wav/ cp "\\$source" "\\$target" rm -rf ${ota.OTA_ROOT} ` } com.systemBrief(`echo "${shell}" > ${ota.OTA_RUN} && chmod +x ${ota.OTA_RUN}`) fileExist = (os.stat(ota.OTA_RUN)[1] === 0) if (!fileExist) { throw new Error('Failed to create upgrade script') } com.systemWithRes(`${ota.OTA_RUN}`) } export default ota