lgq
3 天以前 081f12a52906abe6c2d139fdc144135978681009
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/**
 * 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