2020-07-02 13:58:07 +00:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright 2016-2020 Balena Ltd.
|
|
|
|
*
|
|
|
|
* 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,
|
|
|
|
* 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 { flags } from '@oclif/command';
|
|
|
|
import Command from '../../command';
|
|
|
|
import * as cf from '../../utils/common-flags';
|
|
|
|
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
|
|
|
import { runCommand } from '../../utils/helpers';
|
|
|
|
|
|
|
|
interface FlagsDef {
|
|
|
|
application?: string;
|
|
|
|
app?: string;
|
|
|
|
yes: boolean;
|
|
|
|
advanced: boolean;
|
|
|
|
'os-version'?: string;
|
|
|
|
drive?: string;
|
|
|
|
config?: string;
|
|
|
|
help: void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class DeviceInitCmd extends Command {
|
|
|
|
public static description = stripIndent`
|
|
|
|
Initialise a device with balenaOS.
|
|
|
|
|
|
|
|
Initialise a device by downloading the OS image of a certain application
|
|
|
|
and writing it to an SD Card.
|
|
|
|
|
|
|
|
Note, if the application option is omitted it will be prompted
|
|
|
|
for interactively.
|
|
|
|
`;
|
|
|
|
public static examples = [
|
|
|
|
'$ balena device init',
|
|
|
|
'$ balena device init --application MyApp',
|
|
|
|
];
|
|
|
|
|
|
|
|
public static usage = 'device init';
|
|
|
|
|
|
|
|
public static flags: flags.Input<FlagsDef> = {
|
|
|
|
application: cf.application,
|
|
|
|
app: cf.app,
|
|
|
|
yes: cf.yes,
|
|
|
|
advanced: flags.boolean({
|
|
|
|
char: 'v',
|
|
|
|
description: 'show advanced configuration options',
|
|
|
|
}),
|
|
|
|
'os-version': flags.string({
|
|
|
|
description: stripIndent`
|
|
|
|
exact version number, or a valid semver range,
|
|
|
|
or 'latest' (includes pre-releases),
|
|
|
|
or 'default' (excludes pre-releases if at least one stable version is available),
|
|
|
|
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
|
|
|
|
or 'menu' (will show the interactive menu)
|
|
|
|
`,
|
|
|
|
}),
|
2020-07-15 19:13:18 +00:00
|
|
|
drive: cf.drive,
|
2020-07-02 13:58:07 +00:00
|
|
|
config: flags.string({
|
|
|
|
description: 'path to the config JSON file, see `balena os build-config`',
|
|
|
|
}),
|
|
|
|
help: cf.help,
|
|
|
|
};
|
|
|
|
|
|
|
|
public static authenticated = true;
|
|
|
|
|
|
|
|
public async run() {
|
|
|
|
const { flags: options } = this.parse<FlagsDef, {}>(DeviceInitCmd);
|
|
|
|
|
|
|
|
// Imports
|
2020-08-04 15:24:02 +00:00
|
|
|
const { promisify } = await import('util');
|
2020-07-02 13:58:07 +00:00
|
|
|
const rimraf = promisify(await import('rimraf'));
|
|
|
|
const tmp = await import('tmp');
|
|
|
|
const tmpNameAsync = promisify(tmp.tmpName);
|
|
|
|
tmp.setGracefulCleanup();
|
2020-07-14 09:24:41 +00:00
|
|
|
const { downloadOSImage } = await import('../../utils/cloud');
|
2020-11-04 15:42:31 +00:00
|
|
|
const { getApplication } = await import('../../utils/sdk');
|
2020-07-02 13:58:07 +00:00
|
|
|
|
2020-11-04 15:42:31 +00:00
|
|
|
const logger = await Command.getLogger();
|
|
|
|
const balena = getBalenaSdk();
|
2020-07-02 13:58:07 +00:00
|
|
|
|
|
|
|
// Consolidate application options
|
|
|
|
options.application = options.application || options.app;
|
|
|
|
delete options.app;
|
|
|
|
|
|
|
|
// Get application and
|
2020-11-04 15:42:31 +00:00
|
|
|
const application = (await getApplication(
|
|
|
|
balena,
|
2020-07-08 17:21:37 +00:00
|
|
|
options['application'] ||
|
|
|
|
(await (await import('../../utils/patterns')).selectApplication()),
|
2020-07-31 14:35:20 +00:00
|
|
|
{
|
|
|
|
$expand: {
|
|
|
|
is_for__device_type: {
|
|
|
|
$select: 'slug',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)) as ApplicationWithDeviceType;
|
2020-07-02 13:58:07 +00:00
|
|
|
|
|
|
|
// Register new device
|
|
|
|
const deviceUuid = balena.models.device.generateUniqueKey();
|
|
|
|
console.info(`Registering to ${application.app_name}: ${deviceUuid}`);
|
|
|
|
await balena.models.device.register(application.id, deviceUuid);
|
|
|
|
const device = await balena.models.device.get(deviceUuid);
|
|
|
|
|
|
|
|
// Download OS, configure, and flash
|
|
|
|
const tmpPath = (await tmpNameAsync()) as string;
|
|
|
|
try {
|
|
|
|
logger.logDebug(`Downloading OS image...`);
|
2020-07-14 09:24:41 +00:00
|
|
|
const osVersion = options['os-version'] || 'default';
|
2020-07-31 14:35:20 +00:00
|
|
|
const deviceType = application.is_for__device_type[0].slug;
|
|
|
|
await downloadOSImage(deviceType, tmpPath, osVersion);
|
2020-07-02 13:58:07 +00:00
|
|
|
|
|
|
|
logger.logDebug(`Configuring OS image...`);
|
|
|
|
await this.configureOsImage(tmpPath, device.uuid, options);
|
|
|
|
|
|
|
|
logger.logDebug(`Writing OS image...`);
|
2020-07-31 14:35:20 +00:00
|
|
|
await this.writeOsImage(tmpPath, deviceType, options);
|
2020-07-02 13:58:07 +00:00
|
|
|
} catch (e) {
|
|
|
|
// Remove device in failed cases
|
|
|
|
try {
|
|
|
|
logger.logDebug(`Process failed, removing device ${device.uuid}`);
|
|
|
|
await balena.models.device.remove(device.uuid);
|
|
|
|
} catch (e) {
|
|
|
|
// Ignore removal failures, and throw original error
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
} finally {
|
|
|
|
// Remove temp download
|
|
|
|
logger.logDebug(`Removing temporary OS image download...`);
|
|
|
|
await rimraf(tmpPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Done');
|
|
|
|
return device.uuid;
|
|
|
|
}
|
|
|
|
|
|
|
|
async configureOsImage(path: string, uuid: string, options: FlagsDef) {
|
2020-07-03 08:05:40 +00:00
|
|
|
const configureCommand = ['os', 'configure', path, '--device', uuid];
|
2020-07-02 13:58:07 +00:00
|
|
|
if (options.config) {
|
2020-07-03 08:05:40 +00:00
|
|
|
configureCommand.push('--config', options.config);
|
2020-07-02 13:58:07 +00:00
|
|
|
} else if (options.advanced) {
|
2020-07-03 08:05:40 +00:00
|
|
|
configureCommand.push('--advanced');
|
2020-07-02 13:58:07 +00:00
|
|
|
}
|
|
|
|
await runCommand(configureCommand);
|
|
|
|
}
|
|
|
|
|
|
|
|
async writeOsImage(path: string, deviceType: string, options: FlagsDef) {
|
2020-07-03 08:05:40 +00:00
|
|
|
const osInitCommand = ['os', 'initialize', path, '--type', deviceType];
|
2020-07-02 13:58:07 +00:00
|
|
|
if (options.yes) {
|
2020-07-03 08:05:40 +00:00
|
|
|
osInitCommand.push('--yes');
|
2020-07-02 13:58:07 +00:00
|
|
|
}
|
|
|
|
if (options.drive) {
|
2020-07-03 08:05:40 +00:00
|
|
|
osInitCommand.push('--drive', options.drive);
|
2020-07-02 13:58:07 +00:00
|
|
|
}
|
|
|
|
await runCommand(osInitCommand);
|
|
|
|
}
|
|
|
|
}
|