From 8cc4ce1ae7d932c8b291e7aeb2ebf40d98914853 Mon Sep 17 00:00:00 2001 From: Edwin Joassart Date: Thu, 9 May 2024 18:34:29 +0200 Subject: [PATCH] minor: allow passing custom assets to start SB protected CM4 --- .gitignore | 2 + examples/scanner.ts | 34 ++++++------ examples/usbboot.ts | 96 ++++++++++++++++++++++++--------- lib/scanner/adapters/usbboot.ts | 28 +++++----- package-lock.json | 27 +++++----- package.json | 2 +- 6 files changed, 122 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 0229f81f..b3390665 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build *~ node_modules +yalc.lock +.yalc \ No newline at end of file diff --git a/examples/scanner.ts b/examples/scanner.ts index fcf21620..c77c1706 100644 --- a/examples/scanner.ts +++ b/examples/scanner.ts @@ -13,49 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { platform } from 'os'; +import { platform } from "os"; -import { scanner } from '../lib/'; +import { scanner } from "../lib/"; -async function main() { +const bootImageFolder = String(process.argv.slice(2)); + +async function main(usbBootExtraFolder?: string) { const adapters: scanner.adapters.Adapter[] = [ new scanner.adapters.BlockDeviceAdapter({ includeSystemDrives: () => true, }), - new scanner.adapters.UsbbootDeviceAdapter(), + new scanner.adapters.UsbbootDeviceAdapter(bootImageFolder), ]; - if (platform() === 'win32') { + if (platform() === "win32") { if (scanner.adapters.DriverlessDeviceAdapter !== undefined) { adapters.push(new scanner.adapters.DriverlessDeviceAdapter()); } } const deviceScanner = new scanner.Scanner(adapters); deviceScanner.on( - 'attach', + "attach", async (drive: scanner.adapters.AdapterSourceDestination) => { - console.log('attach', drive); + console.log("attach", drive); if (drive.emitsProgress) { - drive.on('progress', (progress: number) => { - console.log(drive, progress, '%'); + drive.on("progress", (progress: number) => { + console.log(drive, progress, "%"); }); } }, ); deviceScanner.on( - 'detach', + "detach", (drive: scanner.adapters.AdapterSourceDestination) => { - console.log('detach', drive); + console.log("detach", drive); }, ); - deviceScanner.on('error', (error: Error) => { - console.log('error', error); + deviceScanner.on("error", (error: Error) => { + console.log("error", error); }); await deviceScanner.start(); const d = deviceScanner.getBy( - 'devicePath', - 'pci-0000:00:14.0-usb-0:2:1.0-scsi-0:0:0:0', + "devicePath", + "pci-0000:00:14.0-usb-0:2:1.0-scsi-0:0:0:0", ); - console.log('ready', d); + console.log("ready", d); } void main(); diff --git a/examples/usbboot.ts b/examples/usbboot.ts index 1ddfa999..a9e4627f 100644 --- a/examples/usbboot.ts +++ b/examples/usbboot.ts @@ -1,5 +1,6 @@ /* * Copyright 2019 balena.io + * Copyright 2024 balena.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,38 +14,80 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { argv } from 'process'; -import ProgressBar = require('progress'); -import { scanner, sourceDestination } from '../lib/'; +/** + * Unlocking with secureboot + * + * If you need to unlock an cm4 with secureboot, you need to provide a signed boot-image. + * Pass the bootImageFolder flag with the path to the folder containing the signed boot-image. + */ + +import ProgressBar = require("progress"); + +import { scanner, sourceDestination } from "../lib/"; + +import { pipeSourceToDestinationsWithProgressBar } from "./utils"; + +// Parse command line arguments +const args = process.argv.slice(2); // removes 'node' and the script name from the args +const flags: any = {}; + +args.forEach((arg: string, index: number) => { + // Check if the argument is a flag in the format --flag=value + if (arg.startsWith("--")) { + const key: string = arg.substring(2); + const value: string = args[index + 1]; + flags[key] = value; + } +}); + +if (!flags.source) { + console.log("No source has been provided, won't try to flash anything"); +} -import { pipeSourceToDestinationsWithProgressBar } from './utils'; +if (flags.bootImageFolder !== "") { + console.log(`Using external folder ${flags["bootImageFolder"]}`); +} + +if (flags.help) { + console.log( + "Usage: ts-node usbboot.js --bootImageFolder --source ", + ); + console.log( + "Beware, `source` image will be flashed to all USBboot devices, so make sure you know what you are doing", + ); + console.log( + "To unlock a secureboot CM4, set the bootImageFodler to the folder containing a signed boot-image and config.txt", + ); + process.exit(0); +} async function main() { + const bootImageFolder = flags.bootImageFolder; const adapters: scanner.adapters.Adapter[] = [ new scanner.adapters.BlockDeviceAdapter({ includeSystemDrives: () => false, }), - new scanner.adapters.UsbbootDeviceAdapter(), + new scanner.adapters.UsbbootDeviceAdapter(bootImageFolder), ]; const deviceScanner = new scanner.Scanner(adapters); - console.log('Waiting for one compute module'); + console.log("Waiting for one compute module"); // Wait for one compute module to be plugged const computeModule: sourceDestination.UsbbootDrive = await new Promise( (resolve, reject) => { function onAttach(drive: scanner.adapters.AdapterSourceDestination) { if (drive instanceof sourceDestination.UsbbootDrive) { - deviceScanner.removeListener('attach', onAttach); + deviceScanner.removeListener("attach", onAttach); resolve(drive); } } - deviceScanner.on('attach', onAttach); - deviceScanner.on('error', reject); + deviceScanner.on("attach", onAttach); + deviceScanner.on("error", reject); void deviceScanner.start(); }, ); - console.log('Compute module attached'); - const progressBar = new ProgressBar('converting to block device [:bar]', { + console.log("Compute module attached"); + const progressBar = new ProgressBar("converting to block device [:bar]", { total: 100, width: 40, }); @@ -52,43 +95,48 @@ async function main() { const delta = Math.floor(progress) - progressBar.curr; progressBar.tick(delta, {}); } - computeModule.on('progress', onProgress); + computeModule.on("progress", onProgress); // Wait until it is converted to a block device await new Promise((resolve, reject) => { function onDetach(drive: scanner.adapters.AdapterSourceDestination) { if (drive === computeModule) { - deviceScanner.removeListener('detach', onDetach); + deviceScanner.removeListener("detach", onDetach); resolve(); } } - deviceScanner.on('detach', onDetach); - deviceScanner.on('error', reject); + deviceScanner.on("detach", onDetach); + deviceScanner.on("error", reject); }); progressBar.terminate(); - computeModule.removeListener('progress', onProgress); - console.log('Waiting for compute module to reattach as a block device'); + computeModule.removeListener("progress", onProgress); + + console.log("Waiting for compute module to reattach as a block device"); + const dest = await new Promise( (resolve: (drive: sourceDestination.BlockDevice) => void, reject) => { function onAttach(drive: scanner.adapters.AdapterSourceDestination) { if ( drive instanceof sourceDestination.BlockDevice && - drive.description === 'Compute Module' + drive.description === "Compute Module" ) { + drive.oWrite = true; + drive.oDirect = true; resolve(drive); - deviceScanner.removeListener('attach', onAttach); + deviceScanner.removeListener("attach", onAttach); } } - deviceScanner.on('attach', onAttach); - deviceScanner.on('error', reject); + deviceScanner.on("attach", onAttach); + deviceScanner.on("error", reject); }, ); deviceScanner.stop(); - if (argv.length >= 3) { - console.log(`Writing image ${argv[2]}`); + if (flags.source) { + console.log(JSON.stringify(dest)); + console.log(`Writing image ${flags.source} to ${dest.path}`); const source: sourceDestination.SourceDestination = new sourceDestination.File({ - path: argv[2], + path: flags.source, }); void pipeSourceToDestinationsWithProgressBar({ source, diff --git a/lib/scanner/adapters/usbboot.ts b/lib/scanner/adapters/usbboot.ts index e3a48acb..87421265 100644 --- a/lib/scanner/adapters/usbboot.ts +++ b/lib/scanner/adapters/usbboot.ts @@ -17,28 +17,28 @@ import { UsbbootDevice, UsbbootScanner as UsbbootScannerType, -} from 'node-raspberrypi-usbboot'; +} from "node-raspberrypi-usbboot"; -import { getRaspberrypiUsbboot } from '../../lazy'; -import { UsbbootDrive } from '../../source-destination/usbboot'; -import { Adapter } from './adapter'; +import { getRaspberrypiUsbboot } from "../../lazy"; +import { UsbbootDrive } from "../../source-destination/usbboot"; +import { Adapter } from "./adapter"; export class UsbbootDeviceAdapter extends Adapter { private drives: Map = new Map(); private scanner?: UsbbootScannerType; - constructor() { + constructor(usbBootExtraFolder?: string | undefined) { super(); const rpiUsbboot = getRaspberrypiUsbboot(); if (rpiUsbboot !== undefined) { - this.scanner = new rpiUsbboot.UsbbootScanner(); - this.scanner.on('attach', this.onAttach.bind(this)); - this.scanner.on('detach', this.onDetach.bind(this)); - this.scanner.on('ready', this.emit.bind(this, 'ready')); - this.scanner.on('error', this.emit.bind(this, 'error')); + this.scanner = new rpiUsbboot.UsbbootScanner(usbBootExtraFolder); + this.scanner.on("attach", this.onAttach.bind(this)); + this.scanner.on("detach", this.onDetach.bind(this)); + this.scanner.on("ready", this.emit.bind(this, "ready")); + this.scanner.on("error", this.emit.bind(this, "error")); } else { - console.warn('node-raspberrypi-usbboot not available'); - setImmediate(this.emit.bind(this, 'ready')); + console.warn("node-raspberrypi-usbboot not available"); + setImmediate(this.emit.bind(this, "ready")); } } @@ -56,12 +56,12 @@ export class UsbbootDeviceAdapter extends Adapter { drive = new UsbbootDrive(device); this.drives.set(device, drive); } - this.emit('attach', drive); + this.emit("attach", drive); } private onDetach(device: UsbbootDevice): void { const drive = this.drives.get(device); this.drives.delete(device); - this.emit('detach', drive); + this.emit("detach", drive); } } diff --git a/package-lock.json b/package-lock.json index 383a4c79..1fa44d6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "lzma-native": "^8.0.6", "minimatch": "^9.0.3", "mountutils": "^1.3.20", - "node-raspberrypi-usbboot": "1.0.7", + "node-raspberrypi-usbboot": "1.1.0-build-aethernet-allow-custom-sb-assets-2605716d9cad91cee838ca28dba6d94103fe2e31-1", "outdent": "^0.8.0", "partitioninfo": "^6.0.2", "rwmutex": "^1.0.0", @@ -6507,12 +6507,12 @@ } }, "node_modules/node-raspberrypi-usbboot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/node-raspberrypi-usbboot/-/node-raspberrypi-usbboot-1.0.7.tgz", - "integrity": "sha512-ebL2xC7GQSrbrbAdaj2P6rWCViDoh0ewsgu3gHrtOCNeioCZ6ESirUob1iXT/0DCCMqUDPZA0VV3+euCPRruJw==", + "version": "1.1.0-build-aethernet-allow-custom-sb-assets-2605716d9cad91cee838ca28dba6d94103fe2e31-1", + "resolved": "https://registry.npmjs.org/node-raspberrypi-usbboot/-/node-raspberrypi-usbboot-1.1.0-build-aethernet-allow-custom-sb-assets-2605716d9cad91cee838ca28dba6d94103fe2e31-1.tgz", + "integrity": "sha512-BzRK9SHDkbmScUD1Tz3Ci7+9YxD7X4VYFTkqEYU/G/rKfytdb0ghO3I41BBWDTG6rHm+SnorZ/XPkl4E4gXgaQ==", "dependencies": { "debug": "^4.3.4", - "usb": "^2.5.2" + "usb": "^2.12.1" } }, "node_modules/noop-logger": { @@ -8258,13 +8258,13 @@ } }, "node_modules/usb": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/usb/-/usb-2.11.0.tgz", - "integrity": "sha512-u5+NZ6DtoW8TIBtuSArQGAZZ/K15i3lYvZBAYmcgI+RcDS9G50/KPrUd3CrU8M92ahyCvg5e0gc8BDvr5Hwejg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.13.0.tgz", + "integrity": "sha512-pTNKyxD1DfC1DYu8kFcIdpE8f33e0c2Sbmmi0HEs28HTVC555uocvYR1g5DDv4CBibacCh4BqRyYZJylN4mBbw==", "hasInstallScript": true, "dependencies": { "@types/w3c-web-usb": "^1.0.6", - "node-addon-api": "^7.0.0", + "node-addon-api": "^8.0.0", "node-gyp-build": "^4.5.0" }, "engines": { @@ -8272,9 +8272,12 @@ } }, "node_modules/usb/node_modules/node-addon-api": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", - "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.0.0.tgz", + "integrity": "sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/util-deprecate": { "version": "1.0.2", diff --git a/package.json b/package.json index 9e3dde46..9ee21ca6 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "lzma-native": "^8.0.6", "minimatch": "^9.0.3", "mountutils": "^1.3.20", - "node-raspberrypi-usbboot": "1.0.7", + "node-raspberrypi-usbboot": "1.1.0-build-aethernet-allow-custom-sb-assets-2605716d9cad91cee838ca28dba6d94103fe2e31-1", "outdent": "^0.8.0", "partitioninfo": "^6.0.2", "rwmutex": "^1.0.0",