2015-08-14 18:11:49 +00:00
|
|
|
Promise = require('bluebird')
|
2015-05-05 20:32:44 +00:00
|
|
|
capitano = require('capitano')
|
2015-08-12 12:17:46 +00:00
|
|
|
_ = require('lodash')
|
2014-12-19 14:05:54 +00:00
|
|
|
async = require('async')
|
2015-01-08 12:04:37 +00:00
|
|
|
resin = require('resin-sdk')
|
2015-01-21 13:50:19 +00:00
|
|
|
visuals = require('resin-cli-visuals')
|
2015-03-12 16:03:59 +00:00
|
|
|
vcs = require('resin-vcs')
|
2015-05-18 13:34:40 +00:00
|
|
|
manager = require('resin-image-manager')
|
|
|
|
image = require('resin-image')
|
|
|
|
inject = require('resin-config-inject')
|
2015-06-04 12:55:32 +00:00
|
|
|
registerDevice = require('resin-register-device')
|
|
|
|
pine = require('resin-pine')
|
2015-07-22 22:06:53 +00:00
|
|
|
deviceConfig = require('resin-device-config')
|
2015-07-27 12:08:55 +00:00
|
|
|
form = require('resin-cli-form')
|
2015-05-07 15:40:12 +00:00
|
|
|
htmlToText = require('html-to-text')
|
2015-08-17 13:49:59 +00:00
|
|
|
helpers = require('../utils/helpers')
|
2015-04-20 17:03:19 +00:00
|
|
|
|
2015-01-15 17:10:14 +00:00
|
|
|
commandOptions = require('./command-options')
|
2014-11-19 17:38:15 +00:00
|
|
|
|
2015-01-15 17:10:14 +00:00
|
|
|
exports.list =
|
|
|
|
signature: 'devices'
|
|
|
|
description: 'list all devices'
|
|
|
|
help: '''
|
2015-04-27 15:20:53 +00:00
|
|
|
Use this command to list all devices that belong to you.
|
|
|
|
|
|
|
|
You can filter the devices by application by using the `--application` option.
|
2015-01-15 17:10:14 +00:00
|
|
|
|
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-04-27 15:20:53 +00:00
|
|
|
$ resin devices
|
2015-03-23 12:17:55 +00:00
|
|
|
$ resin devices --application MyApp
|
2015-04-27 15:20:53 +00:00
|
|
|
$ resin devices --app MyApp
|
|
|
|
$ resin devices -a MyApp
|
2015-01-15 17:10:14 +00:00
|
|
|
'''
|
2015-04-27 15:20:53 +00:00
|
|
|
options: [ commandOptions.optionalApplication ]
|
2015-01-16 12:34:59 +00:00
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
2015-08-17 13:49:59 +00:00
|
|
|
Promise.try ->
|
|
|
|
if options.application?
|
|
|
|
return resin.models.device.getAllByApplication(options.application)
|
|
|
|
return resin.models.device.getAll()
|
2015-04-27 15:20:53 +00:00
|
|
|
|
2015-08-17 13:49:59 +00:00
|
|
|
.tap (devices) ->
|
2015-07-29 13:34:31 +00:00
|
|
|
console.log visuals.table.horizontal devices, [
|
2015-01-22 17:06:02 +00:00
|
|
|
'id'
|
|
|
|
'name'
|
2015-01-22 17:20:20 +00:00
|
|
|
'device_type'
|
2015-01-22 17:06:02 +00:00
|
|
|
'is_online'
|
|
|
|
'application_name'
|
|
|
|
'status'
|
|
|
|
'last_seen'
|
2015-01-15 17:10:14 +00:00
|
|
|
]
|
2015-08-17 13:49:59 +00:00
|
|
|
.nodeify(done)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
|
|
|
exports.info =
|
2015-07-22 22:06:53 +00:00
|
|
|
signature: 'device <uuid>'
|
2015-01-15 17:10:14 +00:00
|
|
|
description: 'list a single device'
|
|
|
|
help: '''
|
|
|
|
Use this command to show information about a single device.
|
|
|
|
|
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-07-22 22:06:53 +00:00
|
|
|
$ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
2015-01-15 17:10:14 +00:00
|
|
|
'''
|
2015-01-16 12:34:59 +00:00
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.device.get(params.uuid).then (device) ->
|
2015-07-09 13:56:17 +00:00
|
|
|
|
|
|
|
# TODO: We should outsource this logic and probably
|
|
|
|
# other last_seen edge cases to either Resin CLI Visuals
|
|
|
|
# or have it parsed appropriately in the SDK.
|
|
|
|
device.last_seen ?= 'Not seen'
|
|
|
|
|
2015-07-29 13:34:31 +00:00
|
|
|
console.log visuals.table.vertical device, [
|
|
|
|
"$#{device.name}$"
|
2015-01-22 17:06:02 +00:00
|
|
|
'id'
|
2015-01-22 17:20:20 +00:00
|
|
|
'device_type'
|
2015-01-22 17:06:02 +00:00
|
|
|
'is_online'
|
|
|
|
'ip_address'
|
|
|
|
'application_name'
|
|
|
|
'status'
|
|
|
|
'last_seen'
|
|
|
|
'uuid'
|
|
|
|
'commit'
|
|
|
|
'supervisor_version'
|
|
|
|
'is_web_accessible'
|
|
|
|
'note'
|
2015-01-15 17:10:14 +00:00
|
|
|
]
|
2015-07-22 22:06:53 +00:00
|
|
|
.nodeify(done)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
|
|
|
exports.remove =
|
2015-07-22 22:06:53 +00:00
|
|
|
signature: 'device rm <uuid>'
|
2015-01-15 17:10:14 +00:00
|
|
|
description: 'remove a device'
|
|
|
|
help: '''
|
|
|
|
Use this command to remove a device from resin.io.
|
|
|
|
|
|
|
|
Notice this command asks for confirmation interactively.
|
|
|
|
You can avoid this by passing the `--yes` boolean option.
|
|
|
|
|
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-07-22 22:06:53 +00:00
|
|
|
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
|
|
|
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --yes
|
2015-01-15 17:10:14 +00:00
|
|
|
'''
|
|
|
|
options: [ commandOptions.yes ]
|
2015-01-16 12:34:59 +00:00
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
2015-08-17 13:49:59 +00:00
|
|
|
helpers.confirm(options.yes, 'Are you sure you want to delete the device?').then (confirmed) ->
|
|
|
|
return if not confirmed
|
|
|
|
resin.models.device.remove(params.uuid)
|
|
|
|
.nodeify(done)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
|
|
|
exports.identify =
|
|
|
|
signature: 'device identify <uuid>'
|
|
|
|
description: 'identify a device with a UUID'
|
|
|
|
help: '''
|
|
|
|
Use this command to identify a device.
|
|
|
|
|
|
|
|
In the Raspberry Pi, the ACT led is blinked several times.
|
|
|
|
|
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-01-15 17:10:14 +00:00
|
|
|
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
|
|
|
'''
|
2015-01-16 12:34:59 +00:00
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.device.identify(params.uuid).nodeify(done)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
|
|
|
exports.rename =
|
2015-07-22 22:06:53 +00:00
|
|
|
signature: 'device rename <uuid> [newName]'
|
2015-01-15 17:10:14 +00:00
|
|
|
description: 'rename a resin device'
|
|
|
|
help: '''
|
|
|
|
Use this command to rename a device.
|
|
|
|
|
|
|
|
If you omit the name, you'll get asked for it interactively.
|
|
|
|
|
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-07-22 22:06:53 +00:00
|
|
|
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 MyPi
|
|
|
|
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
2015-01-15 17:10:14 +00:00
|
|
|
'''
|
2015-01-16 12:34:59 +00:00
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
2015-08-17 13:49:59 +00:00
|
|
|
Promise.try ->
|
|
|
|
return params.newName if not _.isEmpty(params.newName)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
2015-08-17 13:49:59 +00:00
|
|
|
form.ask
|
|
|
|
message: 'How do you want to name this device?'
|
|
|
|
type: 'input'
|
2015-06-11 16:46:56 +00:00
|
|
|
|
2015-08-17 13:49:59 +00:00
|
|
|
.then(_.partial(resin.models.device.rename, params.uuid))
|
|
|
|
.nodeify(done)
|
2015-01-15 17:10:14 +00:00
|
|
|
|
2015-05-05 05:08:11 +00:00
|
|
|
exports.await =
|
2015-07-22 22:06:53 +00:00
|
|
|
signature: 'device await <uuid>'
|
2015-05-05 05:08:11 +00:00
|
|
|
description: 'await for a device to become online'
|
|
|
|
help: '''
|
|
|
|
Use this command to await for a device to become online.
|
|
|
|
|
|
|
|
The process will exit when the device becomes online.
|
|
|
|
|
|
|
|
Notice that there is no time limit for this command, so it might run forever.
|
|
|
|
|
|
|
|
You can configure the poll interval with the --interval option (defaults to 3000ms).
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
2015-07-22 22:06:53 +00:00
|
|
|
$ resin device await 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
|
|
|
$ resin device await 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --interval 1000
|
2015-05-05 05:08:11 +00:00
|
|
|
'''
|
|
|
|
options: [
|
|
|
|
signature: 'interval'
|
|
|
|
parameter: 'interval'
|
|
|
|
description: 'poll interval'
|
|
|
|
alias: 'i'
|
|
|
|
]
|
|
|
|
permission: 'user'
|
|
|
|
action: (params, options, done) ->
|
|
|
|
options.interval ?= 3000
|
|
|
|
|
2015-08-14 18:35:38 +00:00
|
|
|
spinner = new visuals.Spinner("Awaiting device: #{params.uuid}")
|
|
|
|
|
2015-05-05 05:08:11 +00:00
|
|
|
poll = ->
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.device.isOnline(params.uuid).then (isOnline) ->
|
2015-05-05 05:08:11 +00:00
|
|
|
if isOnline
|
2015-08-14 18:35:38 +00:00
|
|
|
spinner.stop()
|
2015-07-22 22:06:53 +00:00
|
|
|
console.info("Device became online: #{params.uuid}")
|
|
|
|
return
|
2015-05-05 05:08:11 +00:00
|
|
|
else
|
2015-08-14 18:35:38 +00:00
|
|
|
|
|
|
|
# Spinner implementation is smart enough to
|
|
|
|
# not start again if it was already started
|
|
|
|
spinner.start()
|
|
|
|
|
2015-07-22 22:06:53 +00:00
|
|
|
return Promise.delay(options.interval).then(poll)
|
2015-08-14 18:11:49 +00:00
|
|
|
poll().nodeify(done)
|
2015-05-05 05:08:11 +00:00
|
|
|
|
2015-02-04 18:13:28 +00:00
|
|
|
exports.init =
|
|
|
|
signature: 'device init [device]'
|
|
|
|
description: 'initialise a device with resin os'
|
|
|
|
help: '''
|
2015-02-05 13:56:22 +00:00
|
|
|
Use this command to download the OS image of a certain application and write it to an SD Card.
|
2015-02-04 18:13:28 +00:00
|
|
|
|
2015-02-05 13:56:22 +00:00
|
|
|
Note that this command requires admin privileges.
|
|
|
|
|
|
|
|
If `device` is omitted, you will be prompted to select a device interactively.
|
|
|
|
|
|
|
|
Notice this command asks for confirmation interactively.
|
|
|
|
You can avoid this by passing the `--yes` boolean option.
|
|
|
|
|
2015-05-18 13:34:40 +00:00
|
|
|
You can quiet the progress bar and other logging information by passing the `--quiet` boolean option.
|
2015-02-05 13:56:22 +00:00
|
|
|
|
|
|
|
You need to configure the network type and other settings:
|
|
|
|
|
|
|
|
Ethernet:
|
2015-03-03 14:14:16 +00:00
|
|
|
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
|
2015-02-05 13:56:22 +00:00
|
|
|
|
|
|
|
Wifi:
|
2015-03-03 14:14:16 +00:00
|
|
|
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
|
|
|
|
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
|
2015-02-05 13:56:22 +00:00
|
|
|
|
2015-03-11 13:05:20 +00:00
|
|
|
You can omit network related options to be asked about them interactively.
|
|
|
|
|
2015-02-05 13:56:22 +00:00
|
|
|
Examples:
|
2015-03-03 14:14:16 +00:00
|
|
|
|
2015-03-23 12:25:45 +00:00
|
|
|
$ resin device init
|
2015-04-20 13:13:15 +00:00
|
|
|
$ resin device init --application MyApp
|
|
|
|
$ resin device init --application MyApp --network ethernet
|
|
|
|
$ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret
|
2015-02-04 18:13:28 +00:00
|
|
|
'''
|
|
|
|
options: [
|
2015-03-12 16:03:59 +00:00
|
|
|
commandOptions.optionalApplication
|
2015-02-04 18:13:28 +00:00
|
|
|
commandOptions.network
|
|
|
|
commandOptions.wifiSsid
|
|
|
|
commandOptions.wifiKey
|
|
|
|
]
|
|
|
|
permission: 'user'
|
2015-05-18 13:34:40 +00:00
|
|
|
root: true
|
2015-02-04 18:13:28 +00:00
|
|
|
action: (params, options, done) ->
|
|
|
|
|
2015-05-18 13:34:40 +00:00
|
|
|
networkOptions =
|
|
|
|
network: options.network
|
|
|
|
wifiSsid: options.ssid
|
|
|
|
wifiKey: options.key
|
|
|
|
|
2015-07-27 12:08:55 +00:00
|
|
|
async.waterfall [
|
2015-02-04 18:13:28 +00:00
|
|
|
|
|
|
|
(callback) ->
|
2015-03-12 16:03:59 +00:00
|
|
|
return callback(null, options.application) if options.application?
|
2015-07-10 17:04:17 +00:00
|
|
|
vcs.getApplicationName(process.cwd()).nodeify(callback)
|
2015-04-20 13:13:15 +00:00
|
|
|
|
|
|
|
(applicationName, callback) ->
|
2015-05-18 13:34:40 +00:00
|
|
|
options.application = applicationName
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.application.has(options.application).nodeify(callback)
|
2015-06-08 16:31:17 +00:00
|
|
|
|
|
|
|
(hasApplication, callback) ->
|
|
|
|
if not hasApplication
|
|
|
|
return callback(new Error("Invalid application: #{options.application}"))
|
|
|
|
|
2015-02-04 18:13:28 +00:00
|
|
|
return callback(null, params.device) if params.device?
|
2015-08-13 13:42:44 +00:00
|
|
|
visuals.drive().nodeify(callback)
|
2015-02-04 18:13:28 +00:00
|
|
|
|
|
|
|
(device, callback) ->
|
|
|
|
params.device = device
|
2015-03-23 22:32:18 +00:00
|
|
|
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
2015-07-27 12:08:55 +00:00
|
|
|
if options.yes
|
|
|
|
return callback(null, true)
|
|
|
|
else
|
|
|
|
form.ask
|
|
|
|
message: message
|
|
|
|
type: 'confirm'
|
|
|
|
default: false
|
|
|
|
.nodeify(callback)
|
2015-02-04 18:13:28 +00:00
|
|
|
|
|
|
|
(confirmed, callback) ->
|
|
|
|
return done() if not confirmed
|
2015-05-18 13:34:40 +00:00
|
|
|
return callback() if networkOptions.network?
|
2015-07-27 12:08:55 +00:00
|
|
|
form.run [
|
|
|
|
message: 'Network Type'
|
|
|
|
name: 'network'
|
|
|
|
type: 'list'
|
|
|
|
choices: [ 'ethernet', 'wifi' ]
|
|
|
|
,
|
|
|
|
message: 'Wifi Ssid'
|
|
|
|
name: 'wifiSsid'
|
|
|
|
type: 'input'
|
|
|
|
when:
|
|
|
|
network: 'wifi'
|
|
|
|
,
|
|
|
|
message: 'Wifi Key'
|
|
|
|
name: 'wifiKey'
|
|
|
|
type: 'input'
|
|
|
|
when:
|
|
|
|
network: 'wifi'
|
|
|
|
]
|
|
|
|
.then (parameters) ->
|
2015-05-18 13:34:40 +00:00
|
|
|
_.extend(networkOptions, parameters)
|
2015-07-27 12:08:55 +00:00
|
|
|
.nodeify(callback)
|
2015-02-04 18:13:28 +00:00
|
|
|
|
2015-05-18 13:34:40 +00:00
|
|
|
(callback) ->
|
|
|
|
console.info("Checking application: #{options.application}")
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.application.get(options.application).nodeify(callback)
|
2015-04-20 17:03:19 +00:00
|
|
|
|
2015-05-18 13:34:40 +00:00
|
|
|
(application, callback) ->
|
2015-06-04 12:55:32 +00:00
|
|
|
async.parallel
|
2015-05-05 20:32:44 +00:00
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
manifest: (callback) ->
|
|
|
|
console.info('Getting device manifest for the application')
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.device.getManifestBySlug(application.device_type).nodeify(callback)
|
2015-06-04 12:55:32 +00:00
|
|
|
|
|
|
|
config: (callback) ->
|
|
|
|
console.info('Fetching application configuration')
|
2015-07-22 22:06:53 +00:00
|
|
|
deviceConfig.get(options.application, networkOptions).nodeify(callback)
|
2015-06-04 12:55:32 +00:00
|
|
|
|
|
|
|
, callback
|
2015-05-18 13:34:40 +00:00
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
(results, callback) ->
|
2015-05-07 15:40:12 +00:00
|
|
|
params.manifest = results.manifest
|
2015-06-04 12:55:32 +00:00
|
|
|
console.info('Associating the device')
|
2015-05-18 13:34:40 +00:00
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
registerDevice.register pine, results.config, (error, device) ->
|
2015-04-20 17:03:19 +00:00
|
|
|
return callback(error) if error?
|
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
# Associate a device
|
|
|
|
results.config.deviceId = device.id
|
|
|
|
results.config.uuid = device.uuid
|
2015-07-14 12:22:44 +00:00
|
|
|
results.config.registered_at = Math.floor(Date.now() / 1000)
|
2015-05-18 13:34:40 +00:00
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
params.uuid = results.config.uuid
|
|
|
|
|
|
|
|
return callback(null, results)
|
|
|
|
|
|
|
|
(results, callback) ->
|
2015-08-18 10:55:04 +00:00
|
|
|
console.info('Initializing device operating system image')
|
2015-06-04 12:55:32 +00:00
|
|
|
|
|
|
|
if process.env.DEBUG
|
|
|
|
console.log(results.config)
|
|
|
|
|
2015-07-29 13:34:31 +00:00
|
|
|
bar = new visuals.Progress('Downloading Device OS')
|
|
|
|
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
|
2015-06-04 12:55:32 +00:00
|
|
|
|
2015-05-07 15:40:12 +00:00
|
|
|
manager.configure params.manifest, results.config, (error, imagePath, removeCallback) ->
|
2015-06-04 12:55:32 +00:00
|
|
|
spinner.stop()
|
|
|
|
return callback(error, imagePath, removeCallback)
|
|
|
|
, (state) ->
|
|
|
|
if state?
|
|
|
|
bar.update(state)
|
|
|
|
else
|
|
|
|
spinner.start()
|
2015-05-18 13:34:40 +00:00
|
|
|
|
|
|
|
(configuredImagePath, removeCallback, callback) ->
|
2015-05-07 15:40:12 +00:00
|
|
|
console.info('The base image was cached to improve initialization time of similar devices')
|
|
|
|
|
2015-05-18 13:34:40 +00:00
|
|
|
console.info('Attempting to write operating system image to drive')
|
|
|
|
|
2015-07-29 13:34:31 +00:00
|
|
|
bar = new visuals.Progress('Writing Device OS')
|
2015-05-18 13:34:40 +00:00
|
|
|
image.write
|
|
|
|
device: params.device
|
|
|
|
image: configuredImagePath
|
|
|
|
progress: _.bind(bar.update, bar)
|
|
|
|
, (error) ->
|
2015-04-20 17:03:19 +00:00
|
|
|
return callback(error) if error?
|
2015-05-18 13:34:40 +00:00
|
|
|
return callback(null, configuredImagePath, removeCallback)
|
|
|
|
|
|
|
|
(temporalImagePath, removeCallback, callback) ->
|
|
|
|
console.info('Image written successfully')
|
|
|
|
removeCallback(callback)
|
2015-02-04 18:13:28 +00:00
|
|
|
|
2015-06-04 12:55:32 +00:00
|
|
|
(callback) ->
|
2015-07-22 22:06:53 +00:00
|
|
|
resin.models.device.get(params.uuid).nodeify(callback)
|
2015-06-04 12:55:32 +00:00
|
|
|
|
|
|
|
(device, callback) ->
|
|
|
|
console.info("Device created: #{device.name}")
|
2015-05-07 15:40:12 +00:00
|
|
|
return callback(null, params.uuid)
|
|
|
|
|
2015-07-27 12:08:55 +00:00
|
|
|
], done
|