2016-01-04 03:58:51 +00:00
|
|
|
###
|
2019-10-31 01:46:14 +00:00
|
|
|
Copyright 2016-2019 Balena
|
2016-01-04 03:58:51 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
###
|
|
|
|
|
2015-10-14 21:49:27 +00:00
|
|
|
commandOptions = require('./command-options')
|
2017-06-09 10:44:58 +00:00
|
|
|
_ = require('lodash')
|
2020-02-27 14:55:30 +00:00
|
|
|
{ getBalenaSdk } = require('../utils/lazy')
|
2015-09-29 17:03:14 +00:00
|
|
|
|
2017-03-22 10:50:06 +00:00
|
|
|
formatVersion = (v, isRecommended) ->
|
|
|
|
result = "v#{v}"
|
|
|
|
if isRecommended
|
|
|
|
result += ' (recommended)'
|
|
|
|
return result
|
|
|
|
|
|
|
|
resolveVersion = (deviceType, version) ->
|
|
|
|
if version isnt 'menu'
|
2017-10-17 15:28:40 +00:00
|
|
|
if version[0] == 'v'
|
|
|
|
version = version.slice(1)
|
2017-03-22 10:50:06 +00:00
|
|
|
return Promise.resolve(version)
|
|
|
|
|
|
|
|
form = require('resin-cli-form')
|
2020-02-27 14:55:30 +00:00
|
|
|
balena = getBalenaSdk()
|
2017-03-22 10:50:06 +00:00
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
balena.models.os.getSupportedVersions(deviceType)
|
2017-03-22 10:50:06 +00:00
|
|
|
.then ({ versions, recommended }) ->
|
|
|
|
choices = versions.map (v) ->
|
|
|
|
value: v
|
|
|
|
name: formatVersion(v, v is recommended)
|
|
|
|
|
|
|
|
return form.ask
|
|
|
|
message: 'Select the OS version:'
|
|
|
|
type: 'list'
|
|
|
|
choices: choices
|
|
|
|
default: recommended
|
|
|
|
|
2017-06-08 19:58:37 +00:00
|
|
|
exports.versions =
|
|
|
|
signature: 'os versions <type>'
|
2018-10-19 14:38:50 +00:00
|
|
|
description: 'show the available balenaOS versions for the given device type'
|
2017-06-08 19:58:37 +00:00
|
|
|
help: '''
|
2018-10-19 14:38:50 +00:00
|
|
|
Use this command to show the available balenaOS versions for a certain device type.
|
|
|
|
Check available types with `balena devices supported`
|
2017-06-08 19:58:37 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
$ balena os versions raspberrypi3
|
2017-06-08 19:58:37 +00:00
|
|
|
'''
|
2020-02-28 13:12:28 +00:00
|
|
|
action: (params, options) ->
|
2020-02-27 14:55:30 +00:00
|
|
|
balena = getBalenaSdk()
|
2017-06-08 19:58:37 +00:00
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
balena.models.os.getSupportedVersions(params.type)
|
2017-06-08 19:58:37 +00:00
|
|
|
.then ({ versions, recommended }) ->
|
|
|
|
versions.forEach (v) ->
|
|
|
|
console.log(formatVersion(v, v is recommended))
|
|
|
|
|
2015-09-29 17:03:14 +00:00
|
|
|
exports.download =
|
|
|
|
signature: 'os download <type>'
|
|
|
|
description: 'download an unconfigured os image'
|
|
|
|
help: '''
|
|
|
|
Use this command to download an unconfigured os image for a certain device type.
|
2018-10-19 14:38:50 +00:00
|
|
|
Check available types with `balena devices supported`
|
2017-03-22 10:50:06 +00:00
|
|
|
|
2017-03-22 10:28:46 +00:00
|
|
|
If version is not specified the newest stable (non-pre-release) version of OS
|
|
|
|
is downloaded if available, or the newest version otherwise (if all existing
|
|
|
|
versions for the given device type are pre-release).
|
2015-09-29 17:03:14 +00:00
|
|
|
|
2017-03-22 10:50:06 +00:00
|
|
|
You can pass `--version menu` to pick the OS version from the interactive menu
|
|
|
|
of all available versions.
|
|
|
|
|
2015-09-29 17:03:14 +00:00
|
|
|
Examples:
|
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
|
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
|
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
|
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
|
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
|
|
|
|
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
|
2015-09-29 17:03:14 +00:00
|
|
|
'''
|
|
|
|
permission: 'user'
|
|
|
|
options: [
|
2017-03-22 10:28:46 +00:00
|
|
|
{
|
|
|
|
signature: 'output'
|
|
|
|
description: 'output path'
|
|
|
|
parameter: 'output'
|
|
|
|
alias: 'o'
|
|
|
|
required: 'You have to specify the output location'
|
|
|
|
}
|
2018-10-15 17:03:21 +00:00
|
|
|
commandOptions.osVersionOrSemver
|
2015-09-29 17:03:14 +00:00
|
|
|
]
|
2020-02-28 13:12:28 +00:00
|
|
|
action: (params, options) ->
|
2017-03-22 10:50:06 +00:00
|
|
|
Promise = require('bluebird')
|
2020-01-24 22:20:39 +00:00
|
|
|
unzip = require('node-unzip-2')
|
2015-12-07 14:32:24 +00:00
|
|
|
fs = require('fs')
|
|
|
|
rindle = require('rindle')
|
2018-10-19 14:38:50 +00:00
|
|
|
manager = require('balena-image-manager')
|
2015-12-07 14:32:24 +00:00
|
|
|
visuals = require('resin-cli-visuals')
|
|
|
|
|
2015-09-29 17:03:14 +00:00
|
|
|
console.info("Getting device operating system for #{params.type}")
|
|
|
|
|
2017-03-22 10:50:06 +00:00
|
|
|
displayVersion = ''
|
|
|
|
Promise.try ->
|
|
|
|
if not options.version
|
|
|
|
console.warn('OS version is not specified, using the default version:
|
|
|
|
the newest stable (non-pre-release) version if available,
|
|
|
|
or the newest version otherwise (if all existing
|
|
|
|
versions for the given device type are pre-release).')
|
|
|
|
return 'default'
|
|
|
|
return resolveVersion(params.type, options.version)
|
|
|
|
.then (version) ->
|
|
|
|
if version isnt 'default'
|
|
|
|
displayVersion = " #{version}"
|
|
|
|
return manager.get(params.type, version)
|
|
|
|
.then (stream) ->
|
2017-03-22 10:28:46 +00:00
|
|
|
bar = new visuals.Progress("Downloading Device OS#{displayVersion}")
|
|
|
|
spinner = new visuals.Spinner("Downloading Device OS#{displayVersion} (size unknown)")
|
2015-09-29 17:03:14 +00:00
|
|
|
|
|
|
|
stream.on 'progress', (state) ->
|
|
|
|
if state?
|
|
|
|
bar.update(state)
|
|
|
|
else
|
|
|
|
spinner.start()
|
|
|
|
|
|
|
|
stream.on 'end', ->
|
|
|
|
spinner.stop()
|
|
|
|
|
2015-09-30 14:16:24 +00:00
|
|
|
# We completely rely on the `mime` custom property
|
|
|
|
# to make this decision.
|
|
|
|
# The actual stream should be checked instead.
|
|
|
|
if stream.mime is 'application/zip'
|
|
|
|
output = unzip.Extract(path: options.output)
|
|
|
|
else
|
|
|
|
output = fs.createWriteStream(options.output)
|
2015-09-29 17:03:14 +00:00
|
|
|
|
2015-10-21 14:17:10 +00:00
|
|
|
return rindle.wait(stream.pipe(output)).return(options.output)
|
2015-09-29 17:03:14 +00:00
|
|
|
.tap (output) ->
|
2015-11-24 03:38:28 +00:00
|
|
|
console.info('The image was downloaded successfully')
|
2015-09-29 17:36:29 +00:00
|
|
|
|
2018-12-14 11:56:11 +00:00
|
|
|
buildConfigForDeviceType = (deviceType, advanced = false) ->
|
2017-06-12 08:42:08 +00:00
|
|
|
form = require('resin-cli-form')
|
|
|
|
helpers = require('../utils/helpers')
|
|
|
|
|
2018-12-14 11:56:11 +00:00
|
|
|
questions = deviceType.options
|
|
|
|
if not advanced
|
|
|
|
advancedGroup = _.find questions,
|
|
|
|
name: 'advanced'
|
|
|
|
isGroup: true
|
|
|
|
|
|
|
|
if advancedGroup?
|
|
|
|
override = helpers.getGroupDefaults(advancedGroup)
|
|
|
|
|
|
|
|
return form.run(questions, { override })
|
2017-06-12 08:42:08 +00:00
|
|
|
|
2018-12-14 11:56:11 +00:00
|
|
|
buildConfig = (image, deviceTypeSlug, advanced = false) ->
|
|
|
|
Promise = require('bluebird')
|
|
|
|
helpers = require('../utils/helpers')
|
2017-06-12 08:42:08 +00:00
|
|
|
|
2018-12-14 11:56:11 +00:00
|
|
|
Promise.resolve(helpers.getManifest(image, deviceTypeSlug))
|
|
|
|
.then (deviceTypeManifest) ->
|
|
|
|
buildConfigForDeviceType(deviceTypeManifest, advanced)
|
2017-06-12 08:42:08 +00:00
|
|
|
|
|
|
|
exports.buildConfig =
|
|
|
|
signature: 'os build-config <image> <device-type>'
|
|
|
|
description: 'build the OS config and save it to the JSON file'
|
|
|
|
help: '''
|
2018-10-19 14:38:50 +00:00
|
|
|
Use this command to prebuild the OS config once and skip the interactive part of `balena os configure`.
|
2017-06-12 08:42:08 +00:00
|
|
|
|
2017-11-16 18:11:17 +00:00
|
|
|
Example:
|
2017-06-12 08:42:08 +00:00
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
$ balena os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
|
2019-05-15 18:15:57 +00:00
|
|
|
$ balena os configure ../path/rpi3.img --device 7cf02a6 --config rpi3-config.json
|
2017-06-12 08:42:08 +00:00
|
|
|
'''
|
|
|
|
permission: 'user'
|
|
|
|
options: [
|
2017-06-14 21:51:56 +00:00
|
|
|
commandOptions.advancedConfig
|
2017-06-12 08:42:08 +00:00
|
|
|
{
|
|
|
|
signature: 'output'
|
|
|
|
description: 'the path to the output JSON file'
|
|
|
|
alias: 'o'
|
|
|
|
required: 'the output path is required'
|
|
|
|
parameter: 'output'
|
|
|
|
}
|
|
|
|
]
|
2020-02-28 13:12:28 +00:00
|
|
|
action: (params, options) ->
|
2017-06-12 08:42:08 +00:00
|
|
|
fs = require('fs')
|
|
|
|
Promise = require('bluebird')
|
|
|
|
writeFileAsync = Promise.promisify(fs.writeFile)
|
|
|
|
|
|
|
|
buildConfig(params.image, params['device-type'], options.advanced)
|
|
|
|
.then (answers) ->
|
|
|
|
writeFileAsync(options.output, JSON.stringify(answers, null, 4))
|
|
|
|
|
|
|
|
INIT_WARNING_MESSAGE = '''
|
|
|
|
Note: Initializing the device may ask for administrative permissions
|
|
|
|
because we need to access the raw devices directly.
|
|
|
|
'''
|
2017-06-09 10:44:58 +00:00
|
|
|
|
2015-09-29 18:52:34 +00:00
|
|
|
exports.initialize =
|
2015-10-15 12:48:34 +00:00
|
|
|
signature: 'os initialize <image>'
|
2015-09-29 18:52:34 +00:00
|
|
|
description: 'initialize an os image'
|
2017-03-24 09:48:14 +00:00
|
|
|
help: """
|
|
|
|
Use this command to initialize a device with previously configured operating system image.
|
|
|
|
|
2017-06-12 08:42:08 +00:00
|
|
|
#{INIT_WARNING_MESSAGE}
|
2015-09-29 18:52:34 +00:00
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
$ balena os initialize ../path/rpi.img --type 'raspberry-pi'
|
2017-03-24 09:48:14 +00:00
|
|
|
"""
|
2015-09-29 18:52:34 +00:00
|
|
|
permission: 'user'
|
2015-10-15 12:14:35 +00:00
|
|
|
options: [
|
|
|
|
commandOptions.yes
|
2015-10-15 12:48:34 +00:00
|
|
|
{
|
|
|
|
signature: 'type'
|
2018-10-19 14:38:50 +00:00
|
|
|
description: 'device type (Check available types with `balena devices supported`)'
|
2015-10-15 12:48:34 +00:00
|
|
|
parameter: 'type'
|
|
|
|
alias: 't'
|
|
|
|
required: 'You have to specify a device type'
|
|
|
|
}
|
2017-06-14 21:51:56 +00:00
|
|
|
commandOptions.drive
|
2015-10-15 12:14:35 +00:00
|
|
|
]
|
2020-02-28 13:12:28 +00:00
|
|
|
action: (params, options) ->
|
2015-12-07 14:32:24 +00:00
|
|
|
Promise = require('bluebird')
|
2017-03-27 09:14:55 +00:00
|
|
|
umountAsync = Promise.promisify(require('umount').umount)
|
2015-12-07 14:32:24 +00:00
|
|
|
form = require('resin-cli-form')
|
|
|
|
patterns = require('../utils/patterns')
|
2016-09-14 17:51:50 +00:00
|
|
|
helpers = require('../utils/helpers')
|
2015-12-07 14:32:24 +00:00
|
|
|
|
2017-03-24 09:48:14 +00:00
|
|
|
console.info("""
|
|
|
|
Initializing device
|
|
|
|
|
2017-06-12 08:42:08 +00:00
|
|
|
#{INIT_WARNING_MESSAGE}
|
2017-03-24 09:48:14 +00:00
|
|
|
""")
|
2018-11-27 08:34:21 +00:00
|
|
|
Promise.resolve(helpers.getManifest(params.image, options.type))
|
2015-09-29 18:52:34 +00:00
|
|
|
.then (manifest) ->
|
|
|
|
return manifest.initialization?.options
|
2015-10-15 12:14:35 +00:00
|
|
|
.then (questions) ->
|
|
|
|
return form.run questions,
|
|
|
|
override:
|
|
|
|
drive: options.drive
|
2015-09-29 18:52:34 +00:00
|
|
|
.tap (answers) ->
|
|
|
|
return if not answers.drive?
|
2017-06-09 09:32:28 +00:00
|
|
|
patterns.confirm(
|
|
|
|
options.yes
|
|
|
|
"This will erase #{answers.drive}. Are you sure?"
|
|
|
|
"Going to erase #{answers.drive}."
|
2019-09-30 19:56:57 +00:00
|
|
|
true
|
2017-06-09 09:32:28 +00:00
|
|
|
)
|
2015-09-29 18:52:34 +00:00
|
|
|
.return(answers.drive)
|
2017-03-27 09:14:55 +00:00
|
|
|
.then(umountAsync)
|
2015-09-29 18:52:34 +00:00
|
|
|
.tap (answers) ->
|
2017-03-24 09:48:14 +00:00
|
|
|
return helpers.sudo([
|
|
|
|
'internal'
|
|
|
|
'osinit'
|
|
|
|
params.image
|
|
|
|
options.type
|
|
|
|
JSON.stringify(answers)
|
|
|
|
])
|
2015-09-29 18:52:34 +00:00
|
|
|
.then (answers) ->
|
|
|
|
return if not answers.drive?
|
2017-03-28 09:09:58 +00:00
|
|
|
|
2018-10-19 14:38:50 +00:00
|
|
|
# TODO: balena local makes use of ejectAsync, see below
|
2017-03-28 09:09:58 +00:00
|
|
|
# DO we need this / should we do that here?
|
|
|
|
|
|
|
|
# getDrive = (drive) ->
|
|
|
|
# driveListAsync().then (drives) ->
|
|
|
|
# selectedDrive = _.find(drives, device: drive)
|
|
|
|
|
|
|
|
# if not selectedDrive?
|
|
|
|
# throw new Error("Drive not found: #{drive}")
|
|
|
|
|
|
|
|
# return selectedDrive
|
|
|
|
# if (os.platform() is 'win32') and selectedDrive.mountpoint?
|
|
|
|
# ejectAsync = Promise.promisify(require('removedrive').eject)
|
|
|
|
# return ejectAsync(selectedDrive.mountpoint)
|
|
|
|
|
2017-03-27 09:14:55 +00:00
|
|
|
umountAsync(answers.drive).tap ->
|
2015-09-29 18:52:34 +00:00
|
|
|
console.info("You can safely remove #{answers.drive} now")
|