Skip to content

Commit

Permalink
minor: allow passing custom assets to start SB protected CM4
Browse files Browse the repository at this point in the history
  • Loading branch information
aethernet committed Jun 6, 2024
1 parent fa41831 commit 378a1e6
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
extends: ["./node_modules/@balena/lint/config/.eslintrc.js"],
root: true,
ignorePatterns: ["node_modules/", "dist/", "examples/"],
ignorePatterns: ["node_modules/", "dist/", "tests/", "examples"],
rules: {
"@typescript-eslint/no-floating-promises": "off",
"no-bitwise": "off",
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
build
*~
node_modules
yalc.lock
.yalc
6 changes: 4 additions & 2 deletions examples/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import { platform } from 'os';

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 (scanner.adapters.DriverlessDeviceAdapter !== undefined) {
Expand Down
62 changes: 55 additions & 7 deletions examples/usbboot.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
/*
* Copyright 2019 balena.io
* Copyright 2024 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { argv } from 'process';

/**
* 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');
}

if (flags.bootImageFolder !== '') {
console.log(`Using external folder ${flags['bootImageFolder']}`);
}

if (flags.help) {
console.log(
'Usage: ts-node usbboot.js --bootImageFolder <bootImageFolder> --source <image>',
);
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');
Expand Down Expand Up @@ -66,14 +109,18 @@ async function main() {
});
progressBar.terminate();
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.oWrite = true;
drive.oDirect = true;
resolve(drive);
deviceScanner.removeListener('attach', onAttach);
}
Expand All @@ -84,11 +131,12 @@ async function main() {
);
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,
Expand Down
28 changes: 14 additions & 14 deletions lib/scanner/adapters/usbboot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UsbbootDevice, UsbbootDrive> = 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"));
}
}

Expand All @@ -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);
}
}
27 changes: 15 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 2 additions & 3 deletions tests/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,8 @@ export async function testImageNoIt(
assert(canCreateSparseReadStream);
const sourceSparseStream = await innerSource.createSparseReadStream();

const sourceSparseStreamBuffer = await sparseStreamToBuffer(
sourceSparseStream,
);
const sourceSparseStreamBuffer =
await sparseStreamToBuffer(sourceSparseStream);
expect(sourceSparseStreamBuffer.length).to.be.at.most(compareToData.length);
expect(sourceSparseStreamBuffer).to.deep.equal(
compareToData.slice(0, sourceSparseStreamBuffer.length),
Expand Down
21 changes: 8 additions & 13 deletions tests/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,11 @@ describe('utils', function () {
describe('BalenaS3SourceBase', function () {
describe('isESRVersion', function () {
it('should return false for non-ESR versions following the original scheme', function () {
[
'2.7.8.dev',
'2.7.8.prod',
'2.80.3.0.dev',
'2.80.3.0.prod',
].forEach((v) => {
expect(BalenaS3SourceBase.isESRVersion(v)).to.be.false;
});
['2.7.8.dev', '2.7.8.prod', '2.80.3.0.dev', '2.80.3.0.prod'].forEach(
(v) => {
expect(BalenaS3SourceBase.isESRVersion(v)).to.be.false;
},
);
});

it('should return false for unified non-ESR versions', function () {
Expand All @@ -83,11 +80,9 @@ describe('utils', function () {
});

it('should return true for ESR versions following the original scheme', function () {
['2020.04.0.prod', '2021.10.1.dev', '2021.10.1.prod'].forEach(
(v) => {
expect(BalenaS3SourceBase.isESRVersion(v)).to.be.true;
},
);
['2020.04.0.prod', '2021.10.1.dev', '2021.10.1.prod'].forEach((v) => {
expect(BalenaS3SourceBase.isESRVersion(v)).to.be.true;
});
});

it('should return true for unified ESR versions', function () {
Expand Down

0 comments on commit 378a1e6

Please sign in to comment.