mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-18 13:26:24 +00:00
Convert local configure
to oclif, typescript
Change-type: patch Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
457eff1d43
commit
4f831ef443
@ -138,8 +138,8 @@ const capitanoDoc = {
|
||||
{
|
||||
title: 'Local',
|
||||
files: [
|
||||
'build/actions-oclif/local/configure.js',
|
||||
'build/actions-oclif/local/flash.js',
|
||||
'build/actions/local/index.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -250,8 +250,8 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
||||
|
||||
- Local
|
||||
|
||||
- [local flash <image>](#local-flash-image)
|
||||
- [local configure <target>](#local-configure-target)
|
||||
- [local flash <image>](#local-flash-image)
|
||||
|
||||
- Deploy
|
||||
|
||||
@ -2161,6 +2161,23 @@ Examples:
|
||||
|
||||
# Local
|
||||
|
||||
## local configure <target>
|
||||
|
||||
Configure or reconfigure a balenaOS drive or image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena local configure /dev/sdc
|
||||
$ balena local configure path/to/image.img
|
||||
|
||||
### Arguments
|
||||
|
||||
#### TARGET
|
||||
|
||||
path of drive or image to configure
|
||||
|
||||
### Options
|
||||
|
||||
## local flash <image>
|
||||
|
||||
Flash a balenaOS image to a drive.
|
||||
@ -2191,15 +2208,6 @@ drive to flash
|
||||
|
||||
answer "yes" to all questions (non interactive use)
|
||||
|
||||
## local configure <target>
|
||||
|
||||
Use this command to configure or reconfigure a balenaOS drive or image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena local configure /dev/sdc
|
||||
$ balena local configure path/to/image.img
|
||||
|
||||
# Deploy
|
||||
|
||||
## build [source]
|
||||
|
328
lib/actions-oclif/local/configure.ts
Normal file
328
lib/actions-oclif/local/configure.ts
Normal file
@ -0,0 +1,328 @@
|
||||
/**
|
||||
* @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 { stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
target: string;
|
||||
}
|
||||
|
||||
export default class LocalConfigureCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
(Re)configure a balenaOS drive or image.
|
||||
|
||||
Configure or reconfigure a balenaOS drive or image.
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena local configure /dev/sdc',
|
||||
'$ balena local configure path/to/image.img',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
{
|
||||
name: 'target',
|
||||
description: 'path of drive or image to configure',
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
public static usage = 'local configure <target>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static root = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(LocalConfigureCmd);
|
||||
|
||||
const Bluebird = await import('bluebird');
|
||||
const path = await import('path');
|
||||
const umount = await import('umount');
|
||||
const umountAsync = Bluebird.promisify(umount.umount);
|
||||
const isMountedAsync = Bluebird.promisify(umount.isMounted);
|
||||
const reconfix = await import('reconfix');
|
||||
const denymount = Bluebird.promisify(await import('denymount'));
|
||||
const Logger = await import('../../utils/logger');
|
||||
|
||||
const logger = Logger.getLogger();
|
||||
|
||||
const configurationSchema = await this.prepareConnectionFile(params.target);
|
||||
|
||||
if (await isMountedAsync(params.target)) {
|
||||
await umountAsync(params.target);
|
||||
}
|
||||
|
||||
const dmOpts: any = {};
|
||||
if (process.pkg) {
|
||||
// when running in a standalone pkg install, the 'denymount'
|
||||
// executable is placed on the same folder as process.execPath
|
||||
dmOpts.executablePath = path.join(
|
||||
path.dirname(process.execPath),
|
||||
'denymount',
|
||||
);
|
||||
}
|
||||
|
||||
const dmHandler = (cb: () => void) =>
|
||||
reconfix
|
||||
.readConfiguration(configurationSchema, params.target)
|
||||
.tap((config: any) => {
|
||||
logger.logDebug('Current config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
})
|
||||
.then((config: any) => this.getConfiguration(config))
|
||||
.tap((config: any) => {
|
||||
logger.logDebug('New config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
})
|
||||
.then(async (answers: any) => {
|
||||
if (!answers.hostname) {
|
||||
await this.removeHostname(configurationSchema);
|
||||
}
|
||||
return reconfix.writeConfiguration(
|
||||
configurationSchema,
|
||||
answers,
|
||||
params.target,
|
||||
);
|
||||
})
|
||||
.asCallback(cb);
|
||||
|
||||
await denymount(params.target, dmHandler, dmOpts);
|
||||
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
readonly BOOT_PARTITION = 1;
|
||||
readonly CONNECTIONS_FOLDER = '/system-connections';
|
||||
|
||||
getConfigurationSchema(connectionFileName?: string) {
|
||||
if (connectionFileName == null) {
|
||||
connectionFileName = 'resin-wifi';
|
||||
}
|
||||
return {
|
||||
mapper: [
|
||||
{
|
||||
template: {
|
||||
persistentLogging: '{{persistentLogging}}',
|
||||
},
|
||||
domain: [['config_json', 'persistentLogging']],
|
||||
},
|
||||
{
|
||||
template: {
|
||||
hostname: '{{hostname}}',
|
||||
},
|
||||
domain: [['config_json', 'hostname']],
|
||||
},
|
||||
{
|
||||
template: {
|
||||
wifi: {
|
||||
ssid: '{{networkSsid}}',
|
||||
},
|
||||
'wifi-security': {
|
||||
psk: '{{networkKey}}',
|
||||
},
|
||||
},
|
||||
domain: [
|
||||
['system_connections', connectionFileName, 'wifi'],
|
||||
['system_connections', connectionFileName, 'wifi-security'],
|
||||
],
|
||||
},
|
||||
],
|
||||
files: {
|
||||
system_connections: {
|
||||
fileset: true,
|
||||
type: 'ini',
|
||||
location: {
|
||||
path: this.CONNECTIONS_FOLDER.slice(1),
|
||||
// Reconfix still uses the older resin-image-fs, so still needs an
|
||||
// object-based partition definition.
|
||||
partition: this.BOOT_PARTITION,
|
||||
},
|
||||
},
|
||||
config_json: {
|
||||
type: 'json',
|
||||
location: {
|
||||
path: 'config.json',
|
||||
partition: this.BOOT_PARTITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
inquirerOptions = (data: any) => [
|
||||
{
|
||||
message: 'Network SSID',
|
||||
type: 'input',
|
||||
name: 'networkSsid',
|
||||
default: data.networkSsid,
|
||||
},
|
||||
{
|
||||
message: 'Network Key',
|
||||
type: 'input',
|
||||
name: 'networkKey',
|
||||
default: data.networkKey,
|
||||
},
|
||||
{
|
||||
message: 'Do you want to set advanced settings?',
|
||||
type: 'confirm',
|
||||
name: 'advancedSettings',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
message: 'Device Hostname',
|
||||
type: 'input',
|
||||
name: 'hostname',
|
||||
default: data.hostname,
|
||||
when(answers: any) {
|
||||
return answers.advancedSettings;
|
||||
},
|
||||
},
|
||||
{
|
||||
message: 'Do you want to enable persistent logging?',
|
||||
type: 'confirm',
|
||||
name: 'persistentLogging',
|
||||
default: data.persistentLogging,
|
||||
when(answers: any) {
|
||||
return answers.advancedSettings;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
getConfiguration = async (data: any) => {
|
||||
const _ = await import('lodash');
|
||||
const inquirer = await import('inquirer');
|
||||
|
||||
// `persistentLogging` can be `undefined`, so we want
|
||||
// to make sure that case defaults to `false`
|
||||
data = _.assign(data, {
|
||||
persistentLogging: data.persistentLogging || false,
|
||||
});
|
||||
|
||||
return inquirer
|
||||
.prompt(this.inquirerOptions(data))
|
||||
.then((answers: any) => _.merge(data, answers));
|
||||
};
|
||||
|
||||
// Taken from https://goo.gl/kr1kCt
|
||||
readonly CONNECTION_FILE = stripIndent`
|
||||
[connection]
|
||||
id=resin-wifi
|
||||
type=wifi
|
||||
|
||||
[wifi]
|
||||
hidden=true
|
||||
mode=infrastructure
|
||||
ssid=My_Wifi_Ssid
|
||||
|
||||
[wifi-security]
|
||||
auth-alg=open
|
||||
key-mgmt=wpa-psk
|
||||
psk=super_secret_wifi_password
|
||||
|
||||
[ipv4]
|
||||
method=auto
|
||||
|
||||
[ipv6]
|
||||
addr-gen-mode=stable-privacy
|
||||
method=auto\
|
||||
`;
|
||||
|
||||
/*
|
||||
* if the `resin-wifi` file exists (previously configured image or downloaded from the UI) it's used and reconfigured
|
||||
* if the `resin-sample.ignore` exists it's copied to `resin-wifi`
|
||||
* if the `resin-sample` exists it's reconfigured (legacy mode, will be removed eventually)
|
||||
* otherwise, the new file is created
|
||||
*/
|
||||
async prepareConnectionFile(target: string) {
|
||||
const _ = await import('lodash');
|
||||
const imagefs = await import('resin-image-fs');
|
||||
|
||||
return imagefs
|
||||
.listDirectory({
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: this.CONNECTIONS_FOLDER,
|
||||
})
|
||||
.then((files: string[]) => {
|
||||
// The required file already exists
|
||||
if (_.includes(files, 'resin-wifi')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||
if (_.includes(files, 'resin-sample.ignore')) {
|
||||
return imagefs
|
||||
.copy(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||
},
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
)
|
||||
.thenReturn(null);
|
||||
}
|
||||
|
||||
// Legacy mode, to be removed later
|
||||
// We return the file name override from this branch
|
||||
// When it is removed the following cleanup should be done:
|
||||
// * delete all the null returns from this method
|
||||
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||
// * drop the final `then` from this method
|
||||
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||
if (_.includes(files, 'resin-sample')) {
|
||||
return 'resin-sample';
|
||||
}
|
||||
|
||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
return imagefs
|
||||
.writeFile(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
this.CONNECTION_FILE,
|
||||
)
|
||||
.thenReturn(null);
|
||||
})
|
||||
.then((connectionFileName) =>
|
||||
this.getConfigurationSchema(connectionFileName || undefined),
|
||||
);
|
||||
}
|
||||
|
||||
async removeHostname(schema: any) {
|
||||
const _ = await import('lodash');
|
||||
schema.mapper = _.reject(schema.mapper, (mapper: any) =>
|
||||
_.isEqual(Object.keys(mapper.template), ['hostname']),
|
||||
);
|
||||
}
|
||||
}
|
@ -65,11 +65,11 @@ export default class ScanCmd extends Command {
|
||||
const { discover } = await import('balena-sync');
|
||||
const prettyjson = await import('prettyjson');
|
||||
const { ExpectedError } = await import('../errors');
|
||||
const { dockerPort, dockerTimeout } = await import(
|
||||
'../actions/local/common'
|
||||
);
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
|
||||
const dockerPort = 2375;
|
||||
const dockerTimeout = 2000;
|
||||
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ScanCmd);
|
||||
|
||||
const discoverTimeout =
|
||||
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||
|
||||
export * as config from './config';
|
||||
export * as help from './help';
|
||||
export * as local from './local';
|
||||
export * as os from './os';
|
||||
export * as push from './push';
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import * as dockerUtils from '../../utils/docker';
|
||||
import { exitWithExpectedError } from '../../errors';
|
||||
import { getChalk, getCliForm } from '../../utils/lazy';
|
||||
|
||||
export const dockerPort = 2375;
|
||||
export const dockerTimeout = 2000;
|
||||
|
||||
export const filterOutSupervisorContainer = function (container) {
|
||||
for (const name of container.Names) {
|
||||
if (
|
||||
name.includes('resin_supervisor') ||
|
||||
name.includes('balena_supervisor')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const selectContainerFromDevice = Bluebird.method(function (
|
||||
deviceIp,
|
||||
filterSupervisor,
|
||||
) {
|
||||
if (filterSupervisor == null) {
|
||||
filterSupervisor = false;
|
||||
}
|
||||
const docker = dockerUtils.createClient({
|
||||
host: deviceIp,
|
||||
port: dockerPort,
|
||||
timeout: dockerTimeout,
|
||||
});
|
||||
|
||||
// List all containers, including those not running
|
||||
return docker.listContainers({ all: true }).then(function (containers) {
|
||||
containers = containers.filter(function (container) {
|
||||
if (!filterSupervisor) {
|
||||
return true;
|
||||
}
|
||||
return filterOutSupervisorContainer(container);
|
||||
});
|
||||
if (_.isEmpty(containers)) {
|
||||
exitWithExpectedError(`No containers found in ${deviceIp}`);
|
||||
}
|
||||
|
||||
return getCliForm().ask({
|
||||
message: 'Select a container',
|
||||
type: 'list',
|
||||
choices: _.map(containers, function (container) {
|
||||
const containerName = container.Names?.[0] || 'Untitled';
|
||||
const shortContainerId = ('' + container.Id).substr(0, 11);
|
||||
|
||||
return {
|
||||
name: `${containerName} (${shortContainerId})`,
|
||||
value: container.Id,
|
||||
};
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export const pipeContainerStream = Bluebird.method(function ({
|
||||
deviceIp,
|
||||
name,
|
||||
outStream,
|
||||
follow,
|
||||
}) {
|
||||
if (follow == null) {
|
||||
follow = false;
|
||||
}
|
||||
const docker = dockerUtils.createClient({ host: deviceIp, port: dockerPort });
|
||||
|
||||
const container = docker.getContainer(name);
|
||||
return container
|
||||
.inspect()
|
||||
.then((containerInfo) => containerInfo?.State?.Running)
|
||||
.then((isRunning) =>
|
||||
container.attach({
|
||||
logs: !follow || !isRunning,
|
||||
stream: follow && isRunning,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
}),
|
||||
)
|
||||
.then((containerStream) => containerStream.pipe(outStream))
|
||||
.catch(function (err) {
|
||||
err = '' + err.statusCode;
|
||||
if (err === '404') {
|
||||
return console.log(
|
||||
getChalk().red.bold(`Container '${name}' not found.`),
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
});
|
@ -1,285 +0,0 @@
|
||||
/*
|
||||
Copyright 2017-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.
|
||||
*/
|
||||
|
||||
const BOOT_PARTITION = 1;
|
||||
const CONNECTIONS_FOLDER = '/system-connections';
|
||||
|
||||
const getConfigurationSchema = function (connnectionFileName) {
|
||||
if (connnectionFileName == null) {
|
||||
connnectionFileName = 'resin-wifi';
|
||||
}
|
||||
return {
|
||||
mapper: [
|
||||
{
|
||||
template: {
|
||||
persistentLogging: '{{persistentLogging}}',
|
||||
},
|
||||
domain: [['config_json', 'persistentLogging']],
|
||||
},
|
||||
{
|
||||
template: {
|
||||
hostname: '{{hostname}}',
|
||||
},
|
||||
domain: [['config_json', 'hostname']],
|
||||
},
|
||||
{
|
||||
template: {
|
||||
wifi: {
|
||||
ssid: '{{networkSsid}}',
|
||||
},
|
||||
'wifi-security': {
|
||||
psk: '{{networkKey}}',
|
||||
},
|
||||
},
|
||||
domain: [
|
||||
['system_connections', connnectionFileName, 'wifi'],
|
||||
['system_connections', connnectionFileName, 'wifi-security'],
|
||||
],
|
||||
},
|
||||
],
|
||||
files: {
|
||||
system_connections: {
|
||||
fileset: true,
|
||||
type: 'ini',
|
||||
location: {
|
||||
path: CONNECTIONS_FOLDER.slice(1),
|
||||
// Reconfix still uses the older resin-image-fs, so still needs an
|
||||
// object-based partition definition.
|
||||
partition: BOOT_PARTITION,
|
||||
},
|
||||
},
|
||||
config_json: {
|
||||
type: 'json',
|
||||
location: {
|
||||
path: 'config.json',
|
||||
partition: BOOT_PARTITION,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const inquirerOptions = (data) => [
|
||||
{
|
||||
message: 'Network SSID',
|
||||
type: 'input',
|
||||
name: 'networkSsid',
|
||||
default: data.networkSsid,
|
||||
},
|
||||
{
|
||||
message: 'Network Key',
|
||||
type: 'input',
|
||||
name: 'networkKey',
|
||||
default: data.networkKey,
|
||||
},
|
||||
{
|
||||
message: 'Do you want to set advanced settings?',
|
||||
type: 'confirm',
|
||||
name: 'advancedSettings',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
message: 'Device Hostname',
|
||||
type: 'input',
|
||||
name: 'hostname',
|
||||
default: data.hostname,
|
||||
when(answers) {
|
||||
return answers.advancedSettings;
|
||||
},
|
||||
},
|
||||
{
|
||||
message: 'Do you want to enable persistent logging?',
|
||||
type: 'confirm',
|
||||
name: 'persistentLogging',
|
||||
default: data.persistentLogging,
|
||||
when(answers) {
|
||||
return answers.advancedSettings;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getConfiguration = function (data) {
|
||||
const _ = require('lodash');
|
||||
const inquirer = require('inquirer');
|
||||
|
||||
// `persistentLogging` can be `undefined`, so we want
|
||||
// to make sure that case defaults to `false`
|
||||
data = _.assign(data, { persistentLogging: data.persistentLogging || false });
|
||||
|
||||
return inquirer
|
||||
.prompt(inquirerOptions(data))
|
||||
.then((answers) => _.merge(data, answers));
|
||||
};
|
||||
|
||||
// Taken from https://goo.gl/kr1kCt
|
||||
const CONNECTION_FILE = `\
|
||||
[connection]
|
||||
id=resin-wifi
|
||||
type=wifi
|
||||
|
||||
[wifi]
|
||||
hidden=true
|
||||
mode=infrastructure
|
||||
ssid=My_Wifi_Ssid
|
||||
|
||||
[wifi-security]
|
||||
auth-alg=open
|
||||
key-mgmt=wpa-psk
|
||||
psk=super_secret_wifi_password
|
||||
|
||||
[ipv4]
|
||||
method=auto
|
||||
|
||||
[ipv6]
|
||||
addr-gen-mode=stable-privacy
|
||||
method=auto\
|
||||
`;
|
||||
|
||||
/*
|
||||
* if the `resin-wifi` file exists (previously configured image or downloaded from the UI) it's used and reconfigured
|
||||
* if the `resin-sample.ignore` exists it's copied to `resin-wifi`
|
||||
* if the `resin-sample` exists it's reconfigured (legacy mode, will be removed eventually)
|
||||
* otherwise, the new file is created
|
||||
*/
|
||||
const prepareConnectionFile = function (target) {
|
||||
const _ = require('lodash');
|
||||
const imagefs = require('resin-image-fs');
|
||||
|
||||
return imagefs
|
||||
.listDirectory({
|
||||
image: target,
|
||||
partition: BOOT_PARTITION,
|
||||
path: CONNECTIONS_FOLDER,
|
||||
})
|
||||
.then(function (files) {
|
||||
// The required file already exists
|
||||
if (_.includes(files, 'resin-wifi')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||
if (_.includes(files, 'resin-sample.ignore')) {
|
||||
return imagefs
|
||||
.copy(
|
||||
{
|
||||
image: target,
|
||||
partition: BOOT_PARTITION,
|
||||
path: `${CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||
},
|
||||
{
|
||||
image: target,
|
||||
partition: BOOT_PARTITION,
|
||||
path: `${CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
)
|
||||
.thenReturn(null);
|
||||
}
|
||||
|
||||
// Legacy mode, to be removed later
|
||||
// We return the file name override from this branch
|
||||
// When it is removed the following cleanup should be done:
|
||||
// * delete all the null returns from this method
|
||||
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||
// * drop the final `then` from this method
|
||||
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||
if (_.includes(files, 'resin-sample')) {
|
||||
return 'resin-sample';
|
||||
}
|
||||
|
||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
return imagefs
|
||||
.writeFile(
|
||||
{
|
||||
image: target,
|
||||
partition: BOOT_PARTITION,
|
||||
path: `${CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
CONNECTION_FILE,
|
||||
)
|
||||
.thenReturn(null);
|
||||
})
|
||||
.then((connectionFileName) => getConfigurationSchema(connectionFileName));
|
||||
};
|
||||
|
||||
const removeHostname = function (schema) {
|
||||
const _ = require('lodash');
|
||||
schema.mapper = _.reject(schema.mapper, (mapper) =>
|
||||
_.isEqual(Object.keys(mapper.template), ['hostname']),
|
||||
);
|
||||
};
|
||||
|
||||
export const configure = {
|
||||
signature: 'local configure <target>',
|
||||
description: '(Re)configure a balenaOS drive or image',
|
||||
help: `\
|
||||
Use this command to configure or reconfigure a balenaOS drive or image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena local configure /dev/sdc
|
||||
$ balena local configure path/to/image.img\
|
||||
`,
|
||||
root: true,
|
||||
action(params) {
|
||||
const Bluebird = require('bluebird');
|
||||
const path = require('path');
|
||||
const umount = require('umount');
|
||||
const umountAsync = Bluebird.promisify(umount.umount);
|
||||
const isMountedAsync = Bluebird.promisify(umount.isMounted);
|
||||
const reconfix = require('reconfix');
|
||||
const denymount = Bluebird.promisify(require('denymount'));
|
||||
|
||||
return prepareConnectionFile(params.target)
|
||||
.tap(() =>
|
||||
isMountedAsync(params.target).then(function (isMounted) {
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
return umountAsync(params.target);
|
||||
}),
|
||||
)
|
||||
.then(function (configurationSchema) {
|
||||
const dmOpts = {};
|
||||
if (process.pkg) {
|
||||
// when running in a standalone pkg install, the 'denymount'
|
||||
// executable is placed on the same folder as process.execPath
|
||||
dmOpts.executablePath = path.join(
|
||||
path.dirname(process.execPath),
|
||||
'denymount',
|
||||
);
|
||||
}
|
||||
const dmHandler = (cb) =>
|
||||
reconfix
|
||||
.readConfiguration(configurationSchema, params.target)
|
||||
.then(getConfiguration)
|
||||
.then(function (answers) {
|
||||
if (!answers.hostname) {
|
||||
removeHostname(configurationSchema);
|
||||
}
|
||||
return reconfix.writeConfiguration(
|
||||
configurationSchema,
|
||||
answers,
|
||||
params.target,
|
||||
);
|
||||
})
|
||||
.asCallback(cb);
|
||||
return denymount(params.target, dmHandler, dmOpts);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Done!');
|
||||
});
|
||||
},
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
Copyright 2017-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.
|
||||
*/
|
||||
|
||||
export { configure } from './configure';
|
@ -62,9 +62,6 @@ capitano.command(actions.config.generate);
|
||||
// ---------- Preload Module ----------
|
||||
capitano.command(actions.preload);
|
||||
|
||||
// ---------- Local balenaOS Module ----------
|
||||
capitano.command(actions.local.configure);
|
||||
|
||||
// ------------ Local build and deploy -------
|
||||
capitano.command(actions.build);
|
||||
capitano.command(actions.deploy);
|
||||
|
@ -173,6 +173,7 @@ export const convertedCommands = [
|
||||
'key:add',
|
||||
'key:rm',
|
||||
'leave',
|
||||
'local:configure',
|
||||
'local:flash',
|
||||
'login',
|
||||
'logout',
|
||||
|
18
typings/reconfix/index.d.ts
vendored
Normal file
18
typings/reconfix/index.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
declare module 'reconfix';
|
Loading…
Reference in New Issue
Block a user