Compare commits

...

881 Commits

Author SHA1 Message Date
8fa6ad0735 Ensure MDNS service definitions are included in standalone binaries 2017-12-14 13:29:44 +01:00
db32d89f1f Add standalone install instructions to the readme 2017-12-14 13:26:07 +01:00
bb2365b3fd Use proper strict settings for automation TS 2017-12-14 13:26:07 +01:00
a9db392580 Fix docs generation when building on windows
Change-Type: patch
2017-12-14 13:26:07 +01:00
d86fcfcda4 Autodeploy built standalone binaries for all platforms to github
Change-Type: minor
2017-12-14 13:26:07 +01:00
7fb03b8511 Add manual script to deploy built CLI binaries to GitHub 2017-12-14 13:26:07 +01:00
3b52f7ba4e Set up a script to automate builds, and support native extensions 2017-12-14 13:26:07 +01:00
69396904c6 Package the CLI into a standalone runnable binary
This has no native modules yet, which means it works on Linux,
but ignoring any ext4 image data. Drivelist will fail for
some windows operations, but most other things should work.

This is only building a folder with a runnable binary, this needs
packaging before it can be distributable.

Change-Type: minor
2017-12-14 13:26:07 +01:00
71e8448fbf Move from open to opn
Change-Type: patch
2017-12-14 13:26:05 +01:00
41ff793372 Auto-merge for PR #721 via VersionBot
Inline the entire resin-cli-auth module
2017-11-27 17:28:43 +00:00
e4432d1a90 v6.10.2 2017-11-27 17:25:41 +00:00
bd6cb04a2b Replace underscore.string usage with lodash 2017-11-27 12:03:04 +02:00
001c8f9601 Inline the entire resin-cli-auth module
This is part of a general push to demodularize any code that isn't
realistically reusable outside resin-cli, to make the codebase easier to
manage and understand. Once this is done, we'll deprecate the original
module itself.

Change-Type: patch
2017-11-27 12:02:57 +02:00
f106b95be2 Auto-merge for PR #720 via VersionBot
Start adding TypeScript to the CLI and stop committing build output
2017-11-27 09:35:42 +00:00
6fbe602b77 v6.10.1 2017-11-27 09:32:58 +00:00
dc549a665b Update to latest latest typescript 2017-11-27 11:25:20 +02:00
46ca62db3e Include lib/ in published package too, to enable sourcemaps 2017-11-23 19:29:07 +02:00
eb68bb1a1a Set up TypeScript compilation, and make a small start on converting the CLI
Change-Type: patch
2017-11-23 19:28:15 +02:00
93d1e3a4a1 Rename gulp build step to coffee 2017-11-23 19:24:03 +02:00
ff2ee59dae Don't commit raw JS build output
This lets us avoid lots of potential conflicts, issues, and confusion,
and keeps reviews simpler and cleaner.

Change-Type: patch
2017-11-23 19:23:59 +02:00
6217b4a6b5 Auto-merge for PR #718 via VersionBot
Allow configuring images for both device and just applications
2017-11-17 10:46:33 +00:00
67fcc6791c v6.10.0 2017-11-17 10:43:54 +00:00
49d78c56fa Print a deprecation message if you use the old os configure format 2017-11-16 19:51:34 +01:00
e38a0c0047 Allow os configure to configure for an app, not just a specific device
This moves to --app and --uuid options, and deprecates the previous
format, but doesn't immediately remove it so this is not a breaking
change.

Connects-To: #691
Change-Type: minor
2017-11-16 19:51:34 +01:00
eef0d9cdbe Print help even for expected errors
Change-Type: patch
2017-11-16 19:51:17 +01:00
08c40195e5 Make sure everything uses the same shared deviceApiKey option 2017-11-16 19:09:20 +01:00
7e306fbce8 Auto-merge for PR #714 via VersionBot
Allow generating device configurating non-interactively
2017-11-16 14:13:08 +00:00
656656bec1 v6.9.0 2017-11-16 14:09:40 +00:00
87f46cb957 Allow non-interactice config generate for simple network settings
Fixes #695
Fixes #410
Change-Type: minor
2017-11-16 15:04:58 +01:00
f7075d7db9 Fix issue where network settings were not used by config generate
Change-Type: patch
2017-11-16 15:03:49 +01:00
2a2d621d6a Auto-merge for PR #717 via VersionBot
Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates
2017-11-16 13:59:18 +00:00
2db6cdd063 v6.8.3 2017-11-16 13:55:44 +00:00
1fafe64579 Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates
This would be a major change if the command was ever successful, but it
appears it hasn't ever worked for any available published version of
ResinOS, so it's not possible that there are users relying on it.

Change-Type: patch
2017-11-15 14:46:22 +01:00
6562eb544c Auto-merge for PR #713 via VersionBot
Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3
2017-11-14 10:30:30 +00:00
3763bf0712 v6.8.2 2017-11-14 10:27:31 +00:00
890a02e2c8 Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3
Change-Type: patch
2017-11-11 12:29:49 +01:00
427664c729 Auto-merge for PR #711 via VersionBot
Avoid AmbiguousApplication errors in device register when an id is used
2017-11-09 15:20:12 +00:00
727a245715 v6.8.1 2017-11-09 15:16:17 +00:00
a2635f47ee Avoid AmbiguousApplication errors in device register when an id is used
Change-Type: patch
Connects-To: #665
2017-11-09 16:03:06 +01:00
d316f67367 Auto-merge for PR #706 via VersionBot
Update resin-preload to 5.0.0 to handle jetson-tx2
2017-10-27 14:39:30 +00:00
ebd1d7e370 v6.8.0 2017-10-27 14:32:34 +00:00
eef192ff68 Allow preloading jetson-tx2 images, improve flasher images detection and remove the --dont-detect-flasher-type-images option.
* update resin-preload to 5.0.0

Connects-To: #705

Change-Type: minor
2017-10-27 12:14:27 +02:00
36d3c92006 Auto-merge for PR #702 via VersionBot
Add preload to the CLI docs
2017-10-25 12:25:50 +00:00
68e31468cc v6.7.4 2017-10-25 11:52:10 +00:00
dfd8b6717d Add preload to the CLI docs
Change-Type: patch
2017-10-25 13:17:07 +02:00
10d688c02d Auto-merge for PR #701 via VersionBot
Allow specifying `--commit=latest` for `resin preload`
2017-10-25 10:21:02 +00:00
737e961979 v6.7.3 2017-10-25 10:17:42 +00:00
3bca36c277 Allow specifying --commit=latest for resin preload
Connects-To: #700

Depends-On: https://github.com/resin-io/resin-preload/pull/137

Change-Type: patch
2017-10-25 12:09:08 +02:00
24b2c83e92 Auto-merge for PR #699 via VersionBot
Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER
2017-10-24 18:01:07 +00:00
266870018a v6.7.2 2017-10-24 17:57:39 +00:00
80bc044415 Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER, by updating it
Connects-To: #698
Change-Type: patch
2017-10-24 19:31:16 +02:00
563628a5a9 Auto-merge for PR #697 via VersionBot
Respect the --dont-check-device-type option, fix error message
2017-10-24 14:59:21 +00:00
385e2c7f7a v6.7.1 2017-10-24 14:56:48 +00:00
19ce4c4cdb Respect the -dont-check-device-type option, fix error message
Connects-To: #696

Change-Type: patch
2017-10-24 16:45:36 +02:00
0b26fda89c Auto-merge for PR #487 via VersionBot
Device api keys
2017-10-18 18:20:15 +00:00
3f692ecbb0 v6.7.0 2017-10-18 18:18:01 +00:00
2d43e47610 Add device api keys warning on device register and os configure 2017-10-18 13:43:16 +02:00
a8f1d16b26 Make resin os configure safe with device keys for all ResinOS versions 2017-10-18 13:43:16 +02:00
8e95757f47 Make resin config generate safe for all ResinOS versions 2017-10-18 13:43:16 +02:00
3fd4f328ab Added a device api key parameter to the os configure command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
97eaf174ec Added a --device-api-key option to the config generate command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
2ef56a9a3f Added a --device-api-key option to the device register command.
Change-Type: minor
2017-10-18 13:43:16 +02:00
93818b1a98 Auto-merge for PR #689 via VersionBot
Fix issue where `os download` would always download prod images
2017-10-18 10:46:19 +00:00
ce70102378 v6.6.13 2017-10-18 10:41:28 +00:00
0e4c6c459c Fix issue where os download would always download prod images
This also adds support for submodules using resin-sdk shared options
rather than resin-sdk-preconfigured.

Change-Type: patch
Connects-To: #688
2017-10-17 21:20:35 +02:00
6b96fe37ba Auto-merge for PR #687 via VersionBot
Update resin-preload to 4.0.2 to support preloading Edison images
2017-10-16 18:55:22 +00:00
e9c7e0e924 v6.6.12 2017-10-16 18:51:54 +00:00
119fa78927 Update resin-preload to 4.0.2 to support preloading Edison images
Change-Type: patch
2017-10-16 19:27:12 +02:00
dad655c9ec Auto-merge for PR #685 via VersionBot
Document how to `resin deploy` to an app as a collaborator
2017-10-13 14:29:14 +00:00
8af392029f v6.6.11 2017-10-13 14:26:20 +00:00
82888de036 Document how to resin deploy to an app as a collaborator
Change-Type: patch
2017-10-13 16:13:46 +02:00
b4a56e1541 Auto-merge for PR #676 via VersionBot
Ensure hostname truly is optional when configuring device images
2017-10-09 10:14:35 +00:00
19b0ec7f8b v6.6.10 2017-10-09 10:12:22 +00:00
3df7bfe700 Ensure hostname truly is optional when configuring device images
Change-Type: patch
2017-10-09 12:03:31 +02:00
d1fd5a6bff Auto-merge for PR #678 via VersionBot
Fix resin preload --splash-image argument handling
2017-10-06 09:22:01 +00:00
43a7e3ddf4 v6.6.9 2017-10-06 09:19:54 +00:00
10976bed43 Fix resin preload --splash-image argument handling
Connects-To: #677

Change-Type: patch
2017-10-06 11:16:06 +02:00
c187c113d9 Auto-merge for PR #675 via VersionBot
Ensure whoami failures (i.e. present but broken tokens) at startup don't break commands
2017-10-06 07:45:00 +00:00
f7c0258145 v6.6.8 2017-10-06 07:42:39 +00:00
eb729d149e Ensure analytics failures (e.g. from broken tokens) at startup don't break commands
Change-Type: patch
2017-10-05 19:03:01 +02:00
39ac28d4ef Auto-merge for PR #666 via VersionBot
Add windows instructions to fix node-gyp installs
2017-09-22 17:17:51 +00:00
989877d541 v6.6.7 2017-09-22 17:14:37 +00:00
3f3af216fd Add instructions for an admin console 2017-09-22 20:10:50 +03:00
9aef632afd Update to resin-sync, which fixes local push on windows
Change-Type: patch
2017-09-22 19:17:28 +03:00
ef6e00bcea Add windows instructions to fix node-gyp installs
Change-Type: patch
2017-09-22 16:27:24 +03:00
55a7dccc74 Merge pull request #659 from resin-io/catch-uncommitted
Move to using the catch-uncommitted npm package
2017-09-15 19:45:45 +02:00
62035fac83 Move to using the catch-uncommitted npm package 2017-09-15 14:36:33 +02:00
950201973b Auto-merge for PR #655 via VersionBot
Create ISSUE_TEMPLATE.md
2017-09-11 14:53:51 +00:00
e431083e84 v6.6.6 2017-09-11 14:45:51 +00:00
1ff9cf02d7 Create ISSUE_TEMPLATE.md
Fixes #529
Change-Type: patch
2017-09-04 20:18:23 +03:00
bd3b9e32a9 Auto-merge for PR #643 via VersionBot
Fix lodash bugs in device move & quickstart
2017-08-31 12:30:48 +00:00
5a620d6c9e v6.6.5 2017-08-31 12:28:28 +00:00
492e35e5c2 Fix lodash bugs in device move & quickstart
Change-Type: patch
2017-08-31 20:22:36 +08:00
8980bc704a Auto-merge for PR #644 via VersionBot
Check for uncommitted output
2017-08-31 12:21:32 +00:00
8b9e78d645 v6.6.4 2017-08-31 12:18:41 +00:00
8f0131cf50 Tweak catch-uncommitted-output script for clarity, after review 2017-08-31 20:12:06 +08:00
47407a84fb Use only npm 4, for now 2017-08-31 20:12:06 +08:00
d858f3fd90 Make sure the catch-uncommitted-output script fails if the build fails 2017-08-31 20:12:06 +08:00
a36f765f1b Catch uncommitted build output automatically in Travis
Connects-To: #573
Change-Type: patch
2017-08-31 20:12:06 +08:00
fd308a5131 Auto-merge for PR #641 via VersionBot
Update README to link to the full CLI command documentation
2017-08-31 11:47:53 +00:00
3052100973 v6.6.3 2017-08-31 11:44:39 +00:00
cfdd4d3d69 Update README to link to the full CLI command documentation
Also update description to match package.json & repo, and remove
quickstart (since it's unstable at the moment).

Change-Type: patch
2017-08-31 19:37:19 +08:00
0a3123a9cf Auto-merge for PR #642 via VersionBot
Use DOCKER_HOST from env if possible, and no connection options are available
2017-08-31 07:24:30 +00:00
5474666f9e v6.6.2 2017-08-31 07:22:43 +00:00
2bbd45e867 Use DOCKER_HOST from env if possible, and no connection options are available
Connects-to: #625
Change-Type: patch
2017-08-31 15:18:32 +08:00
e8c4a9abfd Auto-merge for PR #650 via VersionBot
Update resin-preload to 3.1.4
2017-08-28 09:38:08 +00:00
710a938b3f v6.6.1 2017-08-28 09:34:59 +00:00
be7c1d278e Update resin-preload to 3.1.4
Connects-To: #649

Change-Type: patch
2017-08-28 10:49:08 +02:00
dfcac4a532 Auto-merge for PR #647 via VersionBot
Add a --dont-check-device-type option for `resin preload`
2017-08-28 08:45:41 +00:00
a5128cd49e v6.6.0 2017-08-28 08:42:44 +00:00
223432406d Add a --dont-check-device-type option for resin preload
Connects-To: #646

Change-Type: minor
2017-08-25 12:27:38 +02:00
cafde01886 Auto-merge for PR #645 via VersionBot
Remove resin-preload build filtering workaround.
2017-08-24 12:28:34 +00:00
0158d1da48 v6.5.3 2017-08-24 12:25:11 +00:00
e0d661a1da Remove resin-preload build filtering workaround.
Connects to #640

Change-Type: patch
2017-08-24 12:39:17 +02:00
b152482133 Reformat changelog for versionbot 2017-08-22 19:30:34 +02:00
4cdf3acf42 v6.5.2 2017-08-22 19:22:05 +02:00
314fcd3919 Merge pull request #639 from resin-io/628-add-progress-bars-for-resin-preload
Add progress bars and spinners for resin preload.
2017-08-22 18:44:56 +02:00
b07a394592 Add progress bars and spinners for resin preload.
Connects-To: #628

Change-Type: minor
2017-08-22 18:42:20 +02:00
14c5d938a6 Merge pull request #627 from resin-io/626-dont-preload-the-same-image-twice
Don't try preloading the same build twice in an image.
2017-08-21 19:26:53 +02:00
c6c2f0bedc Don't try preloading the same build twice in an image.
Connects to #626

Change-Type: patch
2017-08-21 19:24:30 +02:00
6882f4bbe8 Fix missed changelog link 2017-08-21 11:37:23 +02:00
74d6cfb8d2 v6.5.1 2017-08-21 11:33:12 +02:00
3b30a7c4b0 Merge pull request #638 from resin-io/636-replace-lodash-object-with-fromPairs
Use _.fromPairs instead of _.object which was removed in lodash 4.0.0
2017-08-21 10:13:05 +02:00
115e46573b Use _.fromPairs instead of _.object which was removed in lodash 4.0.0
Connects-To: #636

Change-Type: patch
2017-08-21 10:01:25 +02:00
2ad789457f Merge pull request #635 from resin-io/prepublishOnly
Move to prepublishOnly to speed up npm install a little
2017-08-18 17:37:46 +02:00
9beb6de7d8 Move to prepublishOnly to speed up npm install a little 2017-08-18 15:56:35 +02:00
74743745c4 v6.5.0 2017-08-18 15:45:45 +02:00
6fae5a2dd9 Merge pull request #631 from resin-io/autodeploy
Set up Travis npm autopublish
2017-08-18 15:40:18 +02:00
4320f33d8e Set up Travis npm autodeploy 2017-08-18 12:41:30 +02:00
435fedfa07 Merge pull request #630 from resin-io/629-fix-resin-preload-when-a-commit-is-provided
Fix resin preload when a commit is provided.
2017-08-18 09:38:40 +02:00
999f269e36 Fix resin preload when a commit is provided.
Connects-To #629

Change-Type: patch
2017-08-18 09:37:37 +02:00
224dfce4a8 Merge pull request #624 from resin-io/remove-babel
Remove unused babel dependency
2017-08-17 11:40:15 +02:00
60634a5ebd Merge pull request #623 from resin-io/upgrade-lodash
Upgrade to lodash v4
2017-08-17 11:38:56 +02:00
f8f1f52662 Remove unused babel dependency 2017-08-16 19:41:56 +02:00
e204707ee0 Upgrade to lodash v4 2017-08-16 18:58:46 +02:00
b28a4a5f99 Merge pull request #621 from resin-io/update-package-description
Update npm package description
2017-08-16 12:45:09 +02:00
340b2d5572 Update npm package description 2017-08-16 11:47:43 +02:00
4dec846256 Merge pull request #617 from resin-io/594-shared-app-lookup
Allow the looking up of applications with the owner username
2017-08-14 22:43:56 +01:00
dc1b3c3239 Allow the looking up of applications with the owner
change-type: minor
Connects-to: #594
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-08-14 17:00:33 +01:00
2fecb8d3e9 Merge pull request #619 from resin-io/618-use-patched-global-tunnel-ng
Use forked global-scoket-ng that doesn't proxy connection to socket files.
2017-08-11 17:18:52 +02:00
4665a72baf Use forked global-scoket-ng that doesn't proxy connection to socket files.
Connects to #618

Change-Type: patch
2017-08-11 17:02:17 +02:00
b208d601c9 Merge pull request #610 from resin-io/609-integrate-docker-preload
Integrate resin-preload
2017-08-11 15:45:05 +02:00
22b3c39b2b Integrate resin-preload
* split docker connection options from lib.utils.docker.appendOptions

Connects to #609
Connects to https://github.com/resin-io/resin-preload/pull/81

Change-Type: minor
2017-08-11 15:43:07 +02:00
30cca93283 v6.4.0 2017-08-11 13:31:53 +02:00
f0d9b615a7 Merge pull request #615 from resin-io/612-remove-inconsistent-tagging-message
Remove inconsistent (and now unneccesary) 'Tagging image as' message
2017-08-11 13:31:16 +02:00
b7e2c2571f Remove inconsistent (and now unneccesary) 'Tagging image as' message
Change-Type: patch
2017-08-11 13:30:44 +02:00
25538a9afb Merge pull request #616 from resin-io/no-docker-warning
Provide a helpful warning when Docker is not installed
2017-08-11 11:34:04 +02:00
5daa682183 Provide a helpful warning when Docker is not installed
Change-Type: patch
2017-08-10 21:40:05 +02:00
bad4493867 Merge pull request #614 from resin-io/603-node-upgrade-help
Help users using old node versions to upgrade
2017-08-10 14:43:29 +02:00
9e6dd57a5c Help users using old node versions to upgrade 2017-08-09 15:20:50 +02:00
1b86741fa2 v6.3.1 2017-08-08 11:37:22 +03:00
6cca43a09e v6.3.0 2017-08-03 18:05:27 +03:00
b622995a5a Merge pull request #606 from resin-io/534-update-resin-sync
update resin-sync
2017-08-03 18:04:13 +03:00
4329857a16 update resin-sync 2017-08-03 18:01:45 +03:00
d803cfab3a v6.2.0 2017-07-27 14:07:05 +03:00
e7d7ca807f Merge pull request #601 from resin-io/597-resin-wifi-config
Support the new OS with resin-sample.ignore connection file
2017-07-27 14:02:29 +03:00
22e0b4b9dc Support the new OS with resin-sample.ignore connection file 2017-07-27 14:01:49 +03:00
759baf3eda v6.1.1 2017-07-18 18:09:32 +02:00
e6dc7f8075 Merge pull request #595 from resin-io/simplify-login-message
Hide the intro quickstart message, for now (until it gets renovated)
2017-07-18 17:02:18 +02:00
1f0bec39d9 Hide the intro quickstart message for now (until it gets renovated) 2017-07-18 16:57:16 +02:00
64b6549fde v6.1.0 2017-06-30 20:49:08 +02:00
fc7e08c886 Merge pull request #586 from resin-io/543-fix-cwd-string
Upgrade resin-sync to fix node 8 bug
2017-06-30 20:45:59 +02:00
4aadfe9326 Upgrade resin-sync to fix node 8 bug
Connects-To: #543
Change-Type: patch
2017-06-30 19:49:35 +02:00
a65868cbbf Tiny patch to move a CHANGELOG line to the right place 2017-06-29 12:44:14 +02:00
8ab528aae4 Merge pull request #570 from resin-io/bump-resin-sync
Bump resin-sync@8.0.0
2017-06-29 11:56:57 +03:00
d93b82a269 Bump resin-sync@8.0.0
- resin sync: do not explicitly disable ControlMaster SSH option
- resin sync: whitelist collaborators

fixes #422
change-type: minor
2017-06-29 11:56:01 +03:00
3095938b0e Merge pull request #569 from resin-io/permit-ssh-controlmaster
Do not explicitly disable ControlMaster option for device SSH connections
2017-06-29 11:54:19 +03:00
91b3442fc9 Do not explicitly disable ControlMaster option for device SSH connections
The backend server that handles `resin ssh` now supports it.

Also removed the option from local ssh connections to devices, where it
basically has no effect (dropbear on devices supports it)

change-type: minor
fixes #568
2017-06-29 11:53:31 +03:00
e660c6ae90 Merge pull request #557 from resin-io/539-fix-clearLine
Fix issue when using resin deploy with non-standard stdin
2017-06-28 20:30:48 +02:00
e2a165ce80 Show a correct [Info] tag with the deploying progress bar 2017-06-28 18:57:49 +02:00
ce5685551d Remove unused function 2017-06-28 18:52:44 +02:00
15e677e9f1 Refactor stream logger to keep streams as state 2017-06-28 18:52:40 +02:00
5ccde3db8e Fix issue when using resin deploy with non-standard stdin
This fixes issues when piping `resin deploy` to a non-TTY, and should
solve issues on Windows too.

Connects-To: #539
2017-06-28 18:40:00 +02:00
cb550f65bb Merge pull request #582 from resin-io/580-fix-emulated-build-arg
Fix issue where emulated builds broke Docker ARG commands
2017-06-27 16:07:05 +02:00
8d3987fc70 Fix issue where emulated builds broke Docker ARG commands 2017-06-27 15:06:06 +02:00
4fa8d86f02 v6.0.0 2017-06-26 13:38:22 +02:00
6b22166887 Merge pull request #577 from jacintoArias/master
Allow --squash option on resin build command
2017-06-26 13:35:35 +02:00
9e555b3dba Updated CHANGELOG.md 2017-06-26 13:34:22 +02:00
bea7b2035a Added --squash option to resin build command 2017-06-26 13:33:50 +02:00
abebf6b4f4 Merge pull request #576 from resin-io/567-require-node-6
Remove Buffer polyfill, require Node v6+, and print warnings in older versions
2017-06-26 13:28:20 +02:00
6182e7c98a Make Node warning a multi-line string 2017-06-26 13:24:54 +02:00
410390a9ae Remove Buffer polyfill, require Node v6+, and print warnings in older versions 2017-06-22 18:59:50 +02:00
11079caf26 v5.11.1 2017-06-22 18:19:53 +02:00
0c6545218a Merge pull request #574 from resin-io/565-log-node-version
Include node version in sentry logs
2017-06-22 18:17:33 +02:00
7fc806c75f Merge pull request #571 from resin-io/566-fix-v4-compat
Add a polyfill to fix local configure in older (<6) Node versions
2017-06-22 18:12:39 +02:00
18533de3da Include node version in sentry logs 2017-06-22 14:48:46 +02:00
c5afe5f2c2 Update build output, after missed recompile 2017-06-22 14:10:16 +02:00
2875bd672e Add a polyfill to fix local configure in older (<6) Node versions 2017-06-22 13:38:38 +02:00
26d123b33d Merge pull request #563 from resin-io/562-fix-tls-connect
Read ca files and convert to string before passing to the docker daemon
2017-06-22 08:41:10 +01:00
5000febf72 Read ca files and convert to string before passing to the docker daemon
Before this commit, the docker daemon would recieve the filename of the
.pem files, which would be interpreted as the body and would fail. This
commit ensures that the actual body of the pem files are sent to the
daemon.

Change-type: patch
Connects-to: #562
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-06-21 11:11:24 +01:00
378f894da3 v5.11.0 2017-06-19 01:16:04 +03:00
c891d690ec update lockfile 2017-06-19 01:14:08 +03:00
8efaec63ff Merge pull request #559 from resin-io/558-absolute-qemu
Make emulated builds reliable in the presence for WORKDIR comands
2017-06-15 19:41:52 +02:00
b756f2a597 Make emulated builds reliable in the presence for WORKDIR comands
Connects-To: #558
Change-Type: patch
2017-06-15 16:36:44 +02:00
c986e142e2 Merge pull request #548 from resin-io/462-noninteractive-device-init
non-interactive device init
2017-06-15 17:06:41 +03:00
8bdacbb11e dedupe the shared options 2017-06-15 16:43:12 +03:00
d2a9aee685 improve available drives listing
make the config a path to the file and not the stringified JSON
2017-06-15 16:43:02 +03:00
77a4c6fdc2 move available-drives command to util 2017-06-15 16:43:02 +03:00
4d935d62fc improve wording 2017-06-15 16:42:46 +03:00
e8b44d7250 Forced update to the newest resin-sdk 2017-06-15 16:40:46 +03:00
5c5cfde49f automated init doc 2017-06-15 16:39:55 +03:00
97480d3aa4 Improve the supported device types listing 2017-06-15 16:39:55 +03:00
4ac8cb1003 rebuild docs 2017-06-15 16:39:55 +03:00
2e7e033bb9 allow prebuilding the device config and reusing it 2017-06-15 16:39:55 +03:00
9fb5b52069 update dependencies 2017-06-15 16:39:55 +03:00
ad940824a6 list detected drives with resin os available-drives 2017-06-15 16:39:55 +03:00
ed83514a2f allow passing --drive to resin device init 2017-06-15 16:39:55 +03:00
8d91a5732a tolerate the --yes param to device init 2017-06-15 16:39:55 +03:00
1cfe64e4a7 choose version during device init, and list versions with resin os versions 2017-06-15 16:39:55 +03:00
24388811ad remove the user requirement for the supported devices list 2017-06-15 16:38:55 +03:00
f465e74a87 Merge pull request #556 from resin-io/deploy-ux-improvements
Resin deploy ux improvements
2017-06-15 14:31:27 +02:00
c8d51d92e7 Show a clear message immediately as the deploy starts, if we're deploying an image.
Change-Type: patch
2017-06-15 12:36:40 +02:00
5a28d4c92f Make cleanup reliable if a local build fails 2017-06-15 12:36:40 +02:00
66a4faeea5 Small improvement to resin deploy docs
Change-Type: patch
2017-06-15 12:36:40 +02:00
2fce0e964b Merge pull request #554 from resin-io/549-gzip-image-deploy
Gzip images when deploying
2017-06-15 12:35:54 +02:00
a29b40eefa Move promise.spread to promise.join for clarity (from review) 2017-06-15 12:34:26 +02:00
cf7bf2cb7d Fix the gzip level for image uploads to a good perf/size balance 2017-06-15 12:01:22 +02:00
df3c5ca07f Gzip while streaming, rather than gzipping the buffer up front
Connects-To: #549
2017-06-15 12:01:22 +02:00
e584dc43f7 Gzip images when uploading in resin deploy
Change-Type: minor
Connects-To: #549
2017-06-15 12:01:22 +02:00
90a5b15dbc Refactor docker stream buffering before start gzipping 2017-06-15 12:01:22 +02:00
390332cd6c Merge pull request #555 from resin-io/553-fix-qemu-path
Ensure emulated builds use the correct relative path to qemu
2017-06-15 12:00:58 +02:00
37ec11bf25 Ensure emulated builds use the correct relative path to qemu
Change-Type: patch
Connects-To: #553
2017-06-14 20:50:24 +02:00
08ce1abd20 Merge pull request #511 from resin-io/emulated-builds
Add emulated build option to resin build
2017-06-14 11:06:34 +01:00
f2862f7fe2 Add emulated build option to resin build
This commit adds the ability to run a Docker build for an architecture
which is not the host architecture, using qemu-linux-user. Currently
this is only supported for linux.

Added:
* Installation of qemu which supports propagated execve flags
* Copying of qemu binary into the build context
* Transposing the given Dockerfile to use the qemu binary
* Intercepting of the build stream, so the output looks *almost* exactly
  the same.

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-06-14 12:06:03 +01:00
e0673c98fc Remove leftover action/util build output 2017-06-09 03:24:34 +02:00
4ab67ed71d changelog 2017-06-08 13:53:41 +03:00
5ea263ef2e Add package-lock.json 2017-06-08 13:52:47 +03:00
31419b399e v5.10.2 2017-05-31 13:08:04 +01:00
9e8b09010f Merge pull request #540 from resin-io/args
resin build: fix mismatch in command line argument signature
2017-05-31 13:05:48 +01:00
974be5cc13 resin build: fix mismatch in command line argument signature
The command line arg was taking `devicetype`, but the rest of the code
uses `deviceType`. Thus it was impossible to specify a device type
in practice to build a `Dockerfile.template`.

Change-type: patch
Signed-off-by: Gergely Imreh <imrehg@gmail.com>
2017-05-30 10:34:32 +01:00
ec386b807f Merge pull request #518 from resin-io/485-update-interval
Change update check interval to once a day
2017-05-24 11:12:54 -07:00
abc183a729 Change update check interval to once a day
This gives the user enough notice to stay well updated, but won't spam
them if they're using resin-cli frequently.

Connects-to: #485
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-23 17:29:23 -07:00
e5ed6fab85 v5.10.1 2017-05-22 21:59:12 +02:00
eb9152255e Merge pull request #536 from resin-io/535-fix-local-ssh
Fix breaking bug in resin local ssh
2017-05-22 21:55:59 +02:00
78ab47b584 Fix breaking bug in resin local ssh 2017-05-22 20:49:34 +02:00
5b651c7821 v5.10.0 2017-05-22 15:44:28 +02:00
95fc5d7785 Merge pull request #517 from resin-io/ssh-proxy
resin ssh proxy support
2017-05-22 13:04:49 +02:00
4d18e92686 changelog 2017-05-20 00:20:22 +03:00
2d729a82a0 fix valid-email path 2017-05-20 00:19:31 +03:00
4b5240d8cd check for proxytunnel presence 2017-05-19 02:10:14 +03:00
6ae59654a0 fix 2017-05-19 01:44:30 +03:00
b88f7a993c escape params 2017-05-19 01:25:01 +03:00
880fb43fd9 some fixes 2017-05-18 15:12:52 +03:00
fa71df7c70 use upstream dependency 2017-05-12 18:09:43 +03:00
bc79832e1d resin ssh proxy support 2017-05-12 18:09:43 +03:00
1d06bc1b4f Merge pull request #524 from resin-io/520-include-command-line
Include full command line arguments in Sentry errors
2017-05-12 13:27:53 +02:00
88d5ec0c94 Merge pull request #525 from resin-io/508-fix-docs
Get docs and the code generating them back in sync
2017-05-12 13:27:35 +02:00
ebd9e92b10 Merge pull request #523 from resin-io/522-username-in-errors
Include the username used in Sentry errors
2017-05-12 12:04:51 +02:00
a5b535753f Include the username used in Sentry errors, to help us debug them 2017-05-12 12:02:26 +02:00
6e5e4bd8a6 Get docs and the code generating them back in sync (from #515, #508) 2017-05-12 12:01:37 +02:00
6e034acf23 Include full command line arguments in Sentry errors, to help us debug them 2017-05-12 11:48:43 +02:00
3a44782c38 Merge pull request #515 from resin-io/build-time-vars
Add ability to specify built-time variables for local build
2017-05-11 14:31:45 +03:00
654ec75598 Renamed build arg option to —buildArg/-B 2017-05-11 14:20:24 +03:00
66876a2c85 Add ability to specify built-time variables for local build
Change-Type: patch
2017-05-11 14:14:43 +03:00
8cb862359b Merge pull request #514 from resin-io/upload-progress-bar
Add progress bar for upload progress
2017-05-11 14:12:21 +03:00
172fa37bd4 Added CHANGELOG entry 2017-05-10 23:06:35 +03:00
fc5640c79d Draw a progress bar for upload progress
Change-Type: patch
2017-05-10 22:28:51 +03:00
db9225f00a Merge pull request #398 from resin-io/395-show-device-dashboard-link
Show device dashboard url in 'resin device' output
2017-05-08 14:05:40 +03:00
c12b59b978 Show device dashboard url in 'resin device' output
change-type: minor
2017-05-08 13:51:06 +03:00
7eeafa935a Merge pull request #510 from resin-io/highlight-cache-usage
Highlight cache usage in resin build
2017-05-04 14:12:40 +01:00
404348f92e Highlight cache usage in resin build
This commit will highlight the usage of the cache when doing a docker
build via `resin build`, which not only helps the user understand what
the build is doing, but also achieves more parity with the cloud
builder.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 14:09:23 +01:00
4d2af251b2 Merge pull request #502 from resin-io/not-enough-unicorns
Fix the not-enough-unicorns bug
2017-05-04 13:22:42 +01:00
d249ac168a Fix the not-enough-unicorns bug
Add successful build indicator in the form of a unicorn.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 13:18:25 +01:00
65eaad2ed5 Merge pull request #493 from resin-io/local-upload-logs
Upload logs when doing both build + deploy
2017-05-04 13:16:01 +01:00
3ff5880ae3 Allow resin-cli deploy to also upload build logs if present
If build is ran through `resin deploy`, then logs will be stored and
uploaded to the database, where the dashboard can display them

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-05-04 13:11:50 +01:00
511d2abe1d Merge pull request #508 from resin-io/507-add-device-types-note
Add note on how to get resin device types
2017-05-02 10:37:30 +01:00
029b7c7164 Add note on how to get resin device types
Connects to #507

Change-Type: patch
2017-05-01 17:14:58 +01:00
afafa22694 v5.9.1 2017-05-01 14:19:21 +03:00
2df4422748 v5.9.0 2017-05-01 14:14:37 +03:00
e099f0b8b3 Merge pull request #505 from resin-io/proxy
proxy support
2017-05-01 14:12:49 +03:00
8866f47805 proxy support 2017-05-01 14:00:50 +03:00
1d8382e91d v5.8.1 2017-04-27 16:24:57 +03:00
110e18bc88 Merge pull request #500 from resin-io/fix-ssh
fix ssh proxy URL retrieval
2017-04-27 16:24:08 +03:00
3df30c8b5a fix ssh proxy URL retrieval 2017-04-27 16:20:32 +03:00
ebfd842ae5 Merge pull request #499 from resin-io/add-local-build-docs
Add documentation for new resin build and resin deploy commands
2017-04-26 14:15:34 +01:00
39b171fd2a Add documentation for new resin build and resin deploy commands
Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-26 13:38:35 +01:00
04c2333a54 v5.8.0 2017-04-26 12:20:30 +01:00
65884c81a4 Merge pull request #498 from resin-io/prettify-build
Add cloud-builder builder output parity to build and deploy
2017-04-25 13:37:26 +01:00
f50ae65560 Add cloud-builder builder output parity to build and deploy
Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-25 13:13:11 +01:00
be0d6d5a99 Merge pull request #497 from resin-io/share-build-opts
Share nocache and tag build options between build and deploy
2017-04-24 20:56:41 +01:00
4fa1a9c1c6 Share nocache and tag build options between build and deploy
`resin build` had access to the `--nocache` and `--tag` options for
building with docker, but `resin deploy` did not. This commit adds the
options to the shared dockerUtils.appendOptions function.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-24 20:05:18 +01:00
284030d83d Merge pull request #495 from resin-io/fix-build-source
Respect source parameter in resin build
2017-04-24 20:04:22 +01:00
9050cb1975 Respect source parameter in resin build
Upon changing the name of the source parameter from `context`, some
places weren't changed, this commit fixes that.

Change-type: patch
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-24 16:41:15 +01:00
d55ce853a7 Merge pull request #466 from resin-io/local-build
Add ability to build and deploy image locally using resin-cli
2017-04-24 10:39:46 +01:00
d3772386bf Add ability to build and deploy image locally using resin-cli
Using `resin build` a user can now build an image on their own docker
daemon. The daemon can be accessed via a local socket, a remote host and
a remote host over a TLS socket. Project type resolution is supported.
Nocache and tagging of images is also supported.

Using `resin deploy` a user can now deploy an image to their fleet. The
image can either be built by `resin-cli`, plain Docker, or from a remote
source.

Change-type: minor
Signed-off-by: Cameron Diver <cameron@resin.io>
2017-04-23 14:31:45 +01:00
2e042499af Merge pull request #490 from resin-io/486-capture-init-errors
log unhandled exceptions
2017-04-21 23:11:56 +03:00
225d3acf9e log unhandled exceptions 2017-04-21 13:10:30 +03:00
75d10286ad v5.7.2 2017-04-18 12:15:11 +03:00
d6e0616c58 Merge pull request #482 from resin-io/node-v4-support
update to modules that have native node v4 support
2017-04-18 12:12:52 +03:00
380a94f0f8 update to modules that have native node v4 support 2017-04-14 14:48:19 +03:00
9da8c5f09a ignore .DS_Store 2017-04-14 11:41:19 +03:00
89b88315e4 Merge pull request #480 from resin-io/479-resin-local-docs
Add resin local docs
2017-04-11 16:15:30 +01:00
11e8ca178c Add resin local docs
Connects to #479

Change-Type: patch
2017-04-11 14:25:54 +01:00
e583e6f04c Merge pull request #476 from resin-io/fix-peer-dep-error
Move to a consistent resin-token by upgrading to new v6 SDK and resin-cli-auth
2017-04-10 12:42:06 +02:00
0cce2a7ab7 Move to a consistent resin-token by upgrading to new v6 SDK and resin-cli-auth 2017-04-07 21:26:56 +02:00
965aa7e4d4 v5.7.1 2017-04-03 16:07:32 +02:00
0ca64b18a2 Merge pull request #471 from resin-io/470-non-semver-os
Handle non-semver OS versions
2017-04-03 16:06:07 +02:00
63e1313f44 Handle non-semver OS versions 2017-04-03 15:36:04 +02:00
96a7a24738 v5.7.0 2017-03-28 14:43:31 +03:00
dede4bb329 Merge pull request #461 from resin-io/452-fix-permissions
isolate the sudo-runnable command
2017-03-28 14:40:51 +03:00
77b30409bb update resin-device-init to pull in etcher-image-write 2017-03-28 14:37:13 +03:00
137473353c remove username request from the signup process 2017-03-28 14:37:11 +03:00
08b3db717e use individual methods promisification instead of promisifyAll 2017-03-27 12:14:55 +03:00
6cf32e445a isolate the sudo-runnable command 2017-03-27 11:43:35 +03:00
857c5204b9 v5.6.1 2017-03-23 15:52:06 +03:00
169609620a Merge pull request #459 from resin-io/fix-unneeded-warning
Fix unneeded warning
2017-03-23 15:51:13 +03:00
8149172eb0 changelog 2017-03-23 15:32:06 +03:00
cba105a41b suppress warning during the device init OS download 2017-03-23 15:27:54 +03:00
69dff0c603 Merge pull request #450 from resin-io/420-add-sentry
Add sentry error tracking
2017-03-23 13:11:15 +01:00
6ffa4070ff Merge pull request #457 from resin-io/replace-gitter-with-forums
Point to forums instead of gitter
2017-03-23 14:10:18 +02:00
f05b04a6a1 Move sentry DSN to config 2017-03-23 13:10:15 +01:00
88d8112402 Add sentry error tracking 2017-03-23 12:59:42 +01:00
f940d7428c Point to forums instead of gitter
change-type: patch
2017-03-23 12:19:59 +02:00
697779868e Merge pull request #437 from alisondavis17/patch-1
Update README.md
2017-03-23 12:19:24 +02:00
d90874dbef v5.6.0 2017-03-23 01:36:08 +03:00
65b2347b57 Merge pull request #453 from resin-io/41-select-os-version
Closes #451
2017-03-23 01:32:15 +03:00
b25034978b use the published depdendencies 2017-03-23 01:24:25 +03:00
a817bb2135 update gitignore 2017-03-22 22:47:26 +03:00
b629c3601e implement the version menu 2017-03-22 15:45:05 +03:00
3619b2f117 allow specifying the version 2017-03-22 13:28:46 +03:00
4231f50c4c download the non-preview version by default 2017-03-22 12:55:55 +03:00
95fff4b7c4 build bare modules 2017-03-22 12:46:06 +03:00
b3aa3d35f7 fix resin local push help message and lint errors 2017-03-21 12:06:05 +03:00
9f3108c5e7 5.5.0 2017-03-10 18:19:45 +00:00
c95a01e34e Merge pull request #444 from resin-io/scan-sudo-and-fixed-latency
Scan sudo and fixed latency
2017-03-10 18:04:56 +00:00
19c51929a9 Generate JS 2017-03-10 17:59:54 +00:00
73dd625ede Require superuser for scan commands, also introduce docker timeout 2017-03-10 17:59:54 +00:00
08db3ace03 Bump resin-sync@7.0.0: use experimental rds which requires superuser permissions 2017-03-10 17:59:54 +00:00
2125cf9649 5.4.0 2017-03-09 23:56:10 +00:00
e5a7fa5617 Merge pull request #439 from resin-io/resin-local-stop
Resin local stop
2017-03-09 23:50:58 +00:00
3324ff4dee Generate JS 2017-03-09 23:49:59 +00:00
7ad468dc54 Implement 'resin local stop' 2017-03-09 23:49:56 +00:00
8474ee726c Update README.md
Change Support section to point to Forums instead of Gitter
2017-03-09 14:46:20 -08:00
29b2f4afa8 Merge pull request #433 from resin-io/filter-missing-dockerinfo-devices
Filter our devices that do not expose docker socket
2017-03-09 21:19:23 +00:00
7aee4d6d7f Filter our devices that do not expose docker socket 2017-03-09 20:54:11 +00:00
7af004501a Merge pull request #430 from resin-io/rdt-merge
Rdt merge
2017-03-09 11:21:25 +00:00
2d09c18d6b Build JS 2017-03-08 23:41:55 +00:00
53bf314820 Remove app create from primary commands 2017-03-08 23:41:35 +00:00
1ae1a15259 Implement 'resin local' 2017-03-08 23:41:35 +00:00
20ed8c9169 Implement 'resin local push' 2017-03-08 23:41:32 +00:00
977e3fb0ff Implement 'resin local ssh' 2017-03-08 23:41:29 +00:00
c5df32f952 Implement 'resin local scan' 2017-03-08 22:43:23 +00:00
f5cd3375f2 Implement 'resin local promote' 2017-03-08 22:43:23 +00:00
3b4c8f2a01 Implement 'resin local logs' 2017-03-08 22:43:23 +00:00
356042557e Implement 'resin local flash' 2017-03-08 22:43:21 +00:00
00753a5776 Implement 'resin local configure' 2017-03-08 18:43:34 +00:00
eea9a2f723 5.3.0 2017-03-03 18:14:48 +02:00
5ebdf4a8ac Merge pull request #424 from resin-io/ssh-handle-undefined-uuid
resin ssh: handle undefined uuid parameter
2017-03-03 17:29:21 +02:00
fb06249b08 resin ssh: handle undefined uuid parameter
change-type: patch
2017-03-03 17:25:51 +02:00
9214cb0d90 Merge pull request #416 from resin-io/support-aufs-resin-sync
Support aufs resin sync
2017-02-21 15:03:59 +02:00
a04c3b9c7b Generate JS and add gulpfile option to suppress diff 2017-02-21 14:45:47 +02:00
2fde6241c2 Support resin sync for remote resin.io AUFS devices
change-type: minor
2017-02-01 15:52:17 +02:00
ffa645f85c Merge pull request #413 from resin-io/sdk-browser
Use the updated SDK
2017-01-27 13:41:17 +01:00
b629ee6164 Move to preconfigured resin sdk 2017-01-25 19:33:43 +01:00
7a4de5357e Fix some issues with Resin-SDK usage after initial testing 2017-01-25 19:25:12 +01:00
5bbb055cd9 Formally depend on the new resin-sdk release 2017-01-25 19:25:12 +01:00
553b96e48f add the CS preamble to the generated files 2017-01-25 19:25:12 +01:00
7a0e8beb07 update for the new resin.models.device.generateUniqueKey 2017-01-25 19:25:12 +01:00
f17cbb1205 use the new SDK factory 2017-01-25 19:25:12 +01:00
b690060bc4 update deps and remove unused 2017-01-25 19:25:12 +01:00
72b2058d27 Merge pull request #418 from resin-io/lekkas-patch-1
docs: rename 'Git Bash' to 'Git for Windows'
2017-01-18 19:46:40 +02:00
bbd617ea76 docs: rename 'Git Bash' to 'Git for Windows' 2017-01-18 19:33:49 +02:00
f9a4f8c375 v5.2.4 2017-01-18 19:14:52 +02:00
5c289a6e00 Merge pull request #417 from resin-io/fix-cmd-exe-doc-error
Fix requirement docs for resin sync and resin ssh
2017-01-18 19:07:36 +02:00
3b439282ae Regenerate JS and add header to suppress diff 2017-01-18 18:04:42 +02:00
d473509675 Docs: fix requirements for resin ssh and resin sync
change-type: patch
2017-01-18 18:04:37 +02:00
49664b815d v5.2.3 2017-01-04 13:54:09 -04:00
06cc863f02 Merge pull request #412 from resin-io/411-fix-add-js-yaml
add missing dependency - fix #411
2016-12-16 17:38:57 +03:00
099cf997cb add missing dependency - fix #411 2016-12-16 17:21:05 +03:00
e8183d4031 v5.2.2 2016-11-01 14:38:55 -04:00
6954da4a24 Merge pull request #407 from resin-io/add-shutdown-command
Actually add the shutdown command
2016-11-01 12:42:21 -04:00
c18e8f1dbd Actually add the shutdown command 2016-11-01 12:42:29 -03:00
741b53ad7e Merge pull request #406 from resin-io/rebuild-coffeescript
Rebuild CoffeeScript
2016-10-28 16:45:56 -04:00
8282785b2a Rebuild CoffeeScript
See https://github.com/resin-io/resin-cli/pull/405#issuecomment-256998739
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-10-28 16:45:18 -04:00
e013986dba v5.2.1 2016-10-28 12:17:25 -04:00
3083ff0640 Merge pull request #405 from resin-io/fix-capitano-error
Fix `Boolean options can't have parameters` error
2016-10-28 12:16:09 -04:00
01cad3c048 Fix Boolean options can't have parameters error
This error was introduced as part of
`9cf42462c029e038e09efc961736946be8bfcb9b`, since the `forceUpdateLock`
option being used in the `reboot` command contains a `parameter`
property despite being declared a boolean.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-10-28 12:11:57 -04:00
c9919a90a8 v5.2.0 2016-10-27 17:47:43 -04:00
0bf6bcc430 Merge pull request #404 from resin-io/reboot-shutdown-force
Add a device shutdown command, and allow forcing reboot and shutdown
2016-10-27 18:42:30 -03:00
9cf42462c0 Add a device shutdown command, and allow forcing reboot and shutdown 2016-10-27 18:39:31 -03:00
0f4eca2ff0 Update resin-sdk to 5.4.0 2016-10-27 12:02:37 -03:00
d414d1c5e3 Merge pull request #403 from resin-io/coffee-script-1.11.1
Update coffee-script to 1.11.1
2016-10-26 12:04:18 -04:00
afe98ff37d Update coffee-script to 1.11.1 2016-10-26 11:55:39 -03:00
ce026ea387 v5.1.0 2016-09-25 19:55:55 -04:00
25f8f3d740 Merge pull request #397 from resin-io/regenerate-docs
Regenerate markdown documentation
2016-09-25 19:52:41 -04:00
f719f5c948 Regenerate markdown documentation
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-25 19:51:32 -04:00
e2cb79edb1 Merge pull request #396 from resin-io/devices-supported
Bring back `devices supported` command
2016-09-25 19:51:02 -04:00
c6e669fa6b Bring back devices supported command
Fixes: https://github.com/resin-io/resin-cli/issues/394
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-25 19:49:06 -04:00
7953f6f690 5.0.0 2016-09-15 19:45:23 +03:00
baf367b276 Merge pull request #367 from resin-io/features/resin-sync-ssh-improvements
resin sync/ssh improvements
2016-09-15 09:21:24 -07:00
7fecb53cdf resin sync/ssh: update docs 2016-09-15 13:09:25 +03:00
b4edb7ed7f resin sync/ssh: generate JS 2016-09-15 13:09:25 +03:00
16a1741374 resin sync: always display device selection dialog when uuid is not passed as an argument
Closes #375
2016-09-15 13:09:25 +03:00
e0a2217b94 resin sync/ssh: always display selection dialog, even for a single online device
Closes #373
2016-09-15 13:09:25 +03:00
7bd8a683b2 resin sync: remove quotes from --source/--destination help as it implies they're required
Closes #372
2016-09-15 13:09:25 +03:00
6b00bbc73a resin sync: add --after option 2016-09-15 13:09:25 +03:00
42d0b52df7 resin ssh: disable ControlMaster ssh option
This change was necessary because our ssh gateway does not
support ControlMaster mode.

Closes #366
2016-09-15 13:09:25 +03:00
97c768edcd resin sync: add --skip-gitignore option. Improve help section wording 2016-09-15 13:09:25 +03:00
10a0924cd7 resin sync: load uuid from .resin-sync.yml if possible 2016-09-15 13:09:25 +03:00
fdb8bf6967 resin sync: add --destination option and require --source if .resin-sync.yml is missing
Closes #359
2016-09-15 13:09:25 +03:00
81d8974213 resin-sync: update to resin-sync@3.0.0 2016-09-15 13:09:25 +03:00
af8d20ea3f resin sync/ssh: only accept uuid as destination
Also change --port option alias to '-p' from '-t'
2016-09-15 13:09:25 +03:00
7d606568f6 v4.5.0 2016-09-14 11:09:43 -07:00
de13337655 Merge pull request #392 from resin-io/manifest-first-partition
Attempt to get device type from the image first partition
2016-09-14 11:08:19 -07:00
8b485b5ad5 Attempt to get device type from the image first partition
New images will ship a `device-type.json` file in the first partition,
which we can use instead of querying the API for certain configuration
and initialisation commands.

If the file is not found, or is malformed, we still fallback to the API.

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-14 11:06:17 -07:00
fbccf8a465 Merge pull request #393 from resin-io/upgrade-resin-device-init
Upgrade `resin-device-init` to v2.1.0
2016-09-14 11:05:28 -07:00
ce50d8b73d Upgrade resin-device-init to v2.1.0
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2016-09-14 11:03:51 -07:00
2088cbe896 v4.4.0 2016-08-11 10:35:19 -04:00
870ce974e0 Merge pull request #387 from resin-io/Add_OS_VERSION
Add OS version to devices,device commands and Supervisor Version to devices command
2016-08-11 10:24:00 -04:00
5f8c261288 v4.3.0 2016-08-11 10:21:41 -04:00
cb386d15aa Add OS version to devices,device commands and Supervisor Version to devices command 2016-08-10 18:42:34 +05:30
5ed26250d2 Merge pull request #384 from resin-io/features/deviceUrl
Implement `device enableDeviceUrl/disableDeviceUrl/hasDeviceUrl/getDeviceUrl`
2016-08-09 16:45:46 -04:00
7b0415a270 Switch to more human-like command name and follow convention from dashboard 2016-08-09 22:44:40 +05:30
3adb8f19bd Implement device enableDeviceUrl/disableDeviceUrl/hasDeviceUrl/getDeviceUrl 2016-08-09 18:45:24 +05:30
d81fbad6f3 Merge pull request #376 from resin-io/feature/global-help-option
Add global --help option
2016-07-31 20:41:28 -04:00
a70e38ef12 Add global --help option 2016-07-29 15:32:12 +03:00
aeba64b1ee v4.2.1 2016-07-26 13:04:21 -04:00
e6e44474a4 Merge pull request #371 from resin-io/upgrade-sdk
Upgrade `resin-sdk` to v5.3.5
2016-07-26 13:03:18 -04:00
ea44c0571b Upgrade resin-sdk to v5.3.5
This version contains a fix for `undefined` logs. See:

- https://github.com/resin-io/resin-sdk/pull/217
- https://github.com/resin-io/resin-device-logs/pull/14

Fixes: https://github.com/resin-io/resin-cli/issues/370
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-07-26 13:01:25 -04:00
f68364b695 Merge pull request #365 from resin-io/feature/sync-ssh-device-autochoose
Feature/sync ssh device autochoose
2016-07-07 22:21:37 +03:00
81a6843c93 resin ssh: Implement device inference and autoselect if there is a single one 2016-07-07 21:51:53 +03:00
b672ff1fa1 resin sync: change argument/help to a more meaningful 'resin sync [destination]' 2016-07-07 20:37:11 +03:00
b4c89e812c Merge pull request #364 from resin-io/feature/sync-filter-offline-devices
resin sync: filter out offline devices in interactive choosing dialog
2016-07-07 13:14:57 -04:00
68808e760e resin sync: filter out offline devices in interactive choosing dialog 2016-07-07 19:12:39 +03:00
69c98f4afb v4.2.0 2016-06-22 16:19:31 -04:00
509fef754e Merge pull request #356 from resin-io/feature/resin-sync-verbose
Support verbose flag for resin sync
2016-06-22 16:18:34 -04:00
6d1d4dc173 Support verbose flag for resin sync 2016-06-22 17:59:14 +03:00
0d7d6de7cd v4.1.0 2016-06-22 09:46:18 -04:00
c5452f9304 Merge pull request #354 from resin-io/feature/resin-ssh-verbose
Support --verbose/-v flag in resin ssh
2016-06-22 09:37:03 -04:00
12854db923 Support --verbose/-v flag in resin ssh
Closes https://github.com/resin-io/resin-cli/issues/353
2016-06-22 14:57:43 +03:00
51f9e18f6a Merge pull request #352 from resin-io/feature/reword-config-help
Reword config help instructions
2016-06-14 12:10:05 -04:00
29c20e32f6 Reword config help instructions
Closes #351
2016-06-14 19:08:25 +03:00
995194fe2c Merge pull request #346 from resin-io/misc/ssh-sync-cmd-exe-warning
Clarify that sync and ssh only support cmd.exe on Windows
2016-05-19 10:17:28 -04:00
7c784909fd Merge pull request #345 from resin-io/change-resin-ssh-loglevel
Change resin ssh loglevel from QUIET to ERROR
2016-05-19 10:17:20 -04:00
a90d568d5c Clarify that sync and ssh only support cmd.exe on Windows
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-05-19 10:10:45 -04:00
a265063fa1 Change resin ssh loglevel from QUIET to ERROR 2016-05-19 16:52:23 +03:00
1c5945d3ae v4.0.3 2016-05-17 10:00:04 -04:00
6062c7a306 Merge pull request #344 from resin-io/feat/ssh-windows-support
Make resin ssh work in Windows cmd.exe
2016-05-09 12:06:31 -04:00
b061644b19 Make resin ssh work in Windows cmd.exe
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-05-06 13:12:55 -04:00
38b97baf02 Add v4.0.2 entry to CHANGELOG
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-27 11:19:35 -04:00
8315bcb7db Improve CHANGELOG v4.0.0 entry prose
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-27 11:19:21 -04:00
e8fa76266f 4.0.2 2016-04-27 01:33:09 +03:00
3db3baeb7c Merge pull request #343 from resin-io/update-resin-sync-version
Update resin sync version to 2.0.2
2016-04-27 01:31:41 +03:00
17550f9bc9 Update resin sync version to 2.0.2 2016-04-27 01:29:45 +03:00
44f80f7a39 v4.0.1 2016-04-26 12:40:20 -04:00
372fa01cf3 Merge pull request #342 from resin-io/fix/unhandled-ssh-exceptions
Return control to capitano at the end of the ssh command
2016-04-26 12:39:01 -04:00
9a515ef4e3 Return control to capitano at the end of the ssh command
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-26 12:37:39 -04:00
6beb6ff44e 4.0.0 2016-04-26 16:17:01 +03:00
c2cd10f6a7 Update changelog 2016-04-26 16:16:02 +03:00
4d3769def8 Merge pull request #337 from resin-io/enter-container-command
Implement resin enter <uuid> command
2016-04-26 16:13:00 +03:00
42bfb3b0cc Implement resin ssh <uuid> command 2016-04-26 16:00:54 +03:00
d9c2717fe4 Merge pull request #340 from resin-io/upgrade-sync-packages
Use resin-sync v2.0.1 and resin-sdk v5.3.0
2016-04-26 15:57:23 +03:00
8e93577f90 Use resin-sync v2.0.1 and resin-sdk v5.3.0 2016-04-26 15:55:21 +03:00
5b7e1b656e Merge pull request #339 from resin-io/feat/infer-or-select-device
Implement a function to attempt to infer a device selection
2016-04-25 14:30:36 -04:00
4a05ce3f53 Attempt to infer the device uuid in resin sync
This PR adds functionality to `resin sync` to try to infer what the
device uuid is as follows:

- If the argument to `resin sync` is an app, get all the devices from
that application. If there is only one, auto-select it, otherwise show
an interactive drive selection widget.

- If the argument to `resin sync` is a uuid, use it directly, without
trying to infer anything.

- If no argument is passed to `resin sync`, display an interactive
selection widget showing all your devices from all your applications.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-25 08:57:19 -04:00
61574a8522 Merge pull request #338 from resin-io/change-sync-description
Change sync description
2016-04-25 08:37:33 -04:00
9400d4027a Update resin-settings-client version 2016-04-25 14:47:15 +03:00
b5ec49dda1 Remove resin sync 'exec after rsync' feature as it's not supported by
ssh gateway
2016-04-25 14:47:05 +03:00
1c66efb4fa v3.0.2 2016-04-08 13:30:40 -04:00
68fca3d030 Merge pull request #336 from resin-io/fix/os-configure-shorter-uuids
Fix shorter uuids not working with the `os configure` command
2016-04-08 13:29:35 -04:00
325304aebe Fix shorter uuids not working with the os configure command
`resin-device-init`, which is used by the `os configure` command was
still running an older SDK version, that didn't support shorter uuids.

Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
2016-04-08 13:28:21 -04:00
a50cf5b198 Merge pull request #335 from resin-io/doc/plugin-development
Link to resin-plugin-hello from README
2016-03-31 10:19:48 -04:00
1b7aeeafc1 Link to resin-plugin-hello from README 2016-03-31 10:11:05 -04:00
6e3c2ef168 v3.0.1 2016-03-29 11:30:11 -04:00
d9b4753690 Merge pull request #334 from resin-io/fix/event-explosion
Log CLI events based on original command signature
2016-03-29 11:29:17 -04:00
ca40d7ca65 Log CLI events based on original command signature
Currently we log a CLI event with the passed command, however this might
include usr params, like a uuid, and therefore cause thousands of
different event names in Mixpanel.
2016-03-29 11:27:53 -04:00
4aa8362be9 v3.0.0 2016-03-28 09:29:31 -04:00
d68b61a913 Merge pull request #333 from resin-io/misc/regenerate-docs
Regenerate docs and include sync
2016-03-28 09:26:08 -04:00
20969ef249 Regenerate docs and include sync 2016-03-28 09:25:40 -04:00
469d35fcc1 Merge pull request #332 from resin-io/feat/sync
Integrate sync plugin in the Resin CLI
2016-03-28 09:22:36 -04:00
e9b8c38eeb Integrate sync plugin in the Resin CLI 2016-03-28 09:21:25 -04:00
d4c44bf350 Merge pull request #331 from resin-io/feat/config-generate-application
Allow generating a config.json from an application with config generate
2016-03-21 15:46:52 -04:00
2d8cf7c479 Allow generating a config.json from an application with config generate
Currently, `config generate` requires a device uuid. The command now
accepts either a uuid or an application name, and generates a
config.json accordingly.
2016-03-21 15:42:54 -04:00
ca6e715bfa Merge pull request #330 from resin-io/upgrade/device-config
Upgrade resin-device-config to v3.0.0
2016-03-21 15:05:28 -04:00
3a839c947e Upgrade resin-device-config to v3.0.0 2016-03-21 15:01:59 -04:00
9896de3c34 Merge pull request #329 from resin-io/feat/config-inject
Implement config inject command
2016-03-18 09:13:19 -04:00
03d7520de2 Implement config inject command
This command allows to user to inject a whole `config.json` file to a
provisioned device.
2016-03-17 16:07:19 -04:00
4fc8b130f8 Merge pull request #328 from resin-io/fix/innacurate-invalid-2fa-code
Only throw "Invalid 2FA code" if we're sure that's the cause
2016-03-17 15:38:15 -04:00
c5692f8b13 Only throw "Invalid 2FA code" if we're sure that's the cause
Currently, such error will be thrown when
`resin.auth.twoFactor.challenge()` rejects, but an invalid code is not
the only thing this function can reject for.
2016-03-17 15:37:02 -04:00
85561b5d50 Merge pull request #326 from resin-io/doc/eaccess-login
Document the case where EACCES is thrown during login
2016-03-09 11:04:17 -04:00
88c0833ae2 Document the case where EACCES is thrown during login 2016-03-09 11:03:45 -04:00
7df78a0c9e Merge pull request #325 from resin-io/misc/update
Force update alert to be always shown
2016-03-08 09:26:22 -04:00
30663b0301 Force update alert to be always shown
If `updateCheckInterval` has any meanginful value, the alert will be
shown one out of ten times, or something like that, making the user
likely to miss updates.

The underlying issue is that `update-notifier`, if it detects a cached
update notification, it deletes it, and only attempts to show it back if
`updateCheckInterval` is greater than `Date.now() - lastUpdateCheck`.
2016-03-08 09:23:03 -04:00
4ffba0ed56 Merge pull request #324 from resin-io/upgrade/dependencies
Upgrade most outdated dependencies
2016-03-07 11:42:10 -04:00
a522c70f92 Upgrade most outdated dependencies 2016-03-07 08:44:10 -04:00
f295837840 v2.7.0 2016-03-07 08:34:32 -04:00
091ae8eb03 Merge pull request #322 from resin-io/feat/device-reboot
Implement device reboot command
2016-03-07 08:33:14 -04:00
6405c6bb6f Implement device reboot command
Fixes: https://github.com/resin-io/resin-cli/issues/319
2016-03-04 09:38:11 -04:00
797122ce37 Merge pull request #320 from resin-io/feat/config-generate
Implement config generate command
2016-02-29 08:40:15 -04:00
f81db5a775 Merge pull request #321 from resin-io/misc/regenerate-docs
Regenerate docs
2016-02-26 22:40:53 -04:00
0f84aea47d Regenerate docs 2016-02-26 22:38:16 -04:00
84ed20d3ec Implement config generate command
This command allows the user to generate a config.json file and either
print it to stdout or save it to a file.
2016-02-26 22:37:15 -04:00
9870727e36 v2.6.2 2016-02-19 08:36:25 -04:00
db3de2137b Remove hardcoded readdir path
This was probably used for debugging at some point and got commited
accidentally.
2016-02-19 08:35:10 -04:00
92c6af91ca Merge pull request #317 from imrehg/badge
badge fixes
2016-02-13 22:18:18 -04:00
d578f62dca escape gitter image link
This is to fix bage link on npmjs.org, hopefully, as currently
it seems it cannot handle real spaces in image URLs.
2016-02-13 09:27:42 +08:00
d4527220c3 fix dependencies badge link 2016-02-13 09:21:08 +08:00
d5797124f5 Merge pull request #316 from resin-io/misc/mbr-error-troubleshotting
Move Invalid MBR error explanation to TROUBLESHOOTING
2016-02-12 19:26:14 -04:00
654c3c627d Move Invalid MBR error explanation to TROUBLESHOOTING 2016-02-12 14:45:00 -04:00
3953b00e77 v2.6.1 2016-02-12 14:41:37 -04:00
13f33da280 Merge pull request #315 from resin-io/feat/capitano-analytics
Inject analytics in Capitano
2016-02-12 14:38:31 -04:00
356d2ef6b2 Inject analytics in Capitano 2016-02-12 14:34:16 -04:00
36a8179e0c Merge pull request #314 from resin-io/upgrade/form
Upgrade resin-cli-form to v1.4.0
2016-02-11 12:54:33 -04:00
aedb9c732f Upgrade resin-cli-form to v1.4.0 2016-02-11 12:49:56 -04:00
c3bd433532 Merge pull request #313 from resin-io/feat/devices-is-online
Show if a device is online in devices command
2016-02-11 09:50:43 -04:00
d72750de65 Show if a device is online in devices command
Fixes: https://github.com/resin-io/resin-cli/issues/312
2016-02-10 08:51:00 -04:00
ccd8e73c4e Merge pull request #310 from resin-io/fix/309/device-name-help
Refer to device uuids in command help
2016-01-28 09:11:25 -04:00
6c677fe8cd Refer to device uuids in command help
Currently the CLI asks for a device "name" on device options while it
actually needs a "uuid".

Fixes: https://github.com/resin-io/resin-cli/issues/309
2016-01-28 09:10:11 -04:00
eaa1a798c5 Merge pull request #308 from resin-io/upgrade/image-manager
Upgrade Resin Image Manager to v4.0.0
2016-01-26 16:07:30 -04:00
da1b446b3b Upgrade Resin Image Manager to v4.0.0 2016-01-26 15:38:58 -04:00
18b4509fef Merge pull request #307 from resin-io/feature/device-status
Show parsed device status in device command
2016-01-26 15:09:03 -04:00
35bba04b16 Show parsed device status in device command 2016-01-26 12:16:55 -04:00
dd382158dd Merge pull request #306 from resin-io/misc/messages
Improve messages
2016-01-25 09:50:56 -04:00
40f015de93 Fix grammar issue: Administration -> Administrative 2016-01-25 09:00:04 -04:00
bee523828a Improve awaitDevice success message 2016-01-25 08:59:09 -04:00
474635401a Merge pull request #305 from resin-io/doc/mbr-error
Document corruped image MBR error
2016-01-24 18:16:35 -04:00
a8c30bb395 Document corruped image MBR error 2016-01-22 10:14:55 -04:00
9e00fdaf31 Merge pull request #304 from resin-io/doc/readme-header
Improve README header style
2016-01-21 23:18:36 -04:00
5f83c870ed Improve README header style
- Use SVG badges.
- Set description as a blockquote before the title.
2016-01-21 23:17:02 -04:00
5d89533afc Merge pull request #303 from resin-io/doc/login-error
Add information on how to login on user permission error
2016-01-21 23:12:20 -04:00
a346c3f043 Add information on how to login on user permission error 2016-01-21 23:07:08 -04:00
de0649c980 v2.6.0 2016-01-21 15:59:52 -04:00
b7300deab7 Merge pull request #302 from resin-io/upgrade/manager
Upgrade Resin Image Manager to v3.2.6
2016-01-21 15:50:45 -04:00
70b2ba3ab9 Upgrade Resin Image Manager to v3.2.6 2016-01-21 15:49:44 -04:00
8cbf792786 Merge pull request #301 from resin-io/doc/remove-ci-badges
Remove build status CI badges from README
2016-01-21 10:43:46 -04:00
bca9a7f51f Remove build status CI badges from README
CI integration was removed in
96f0b5fbd6,
but it looks like we forgot to remove the badges.
2016-01-21 10:42:45 -04:00
4bf079377b Merge pull request #300 from resin-io/feat/shorter-uuids
Support shorter uuids
2016-01-21 10:41:55 -04:00
ebefd816b6 Show shorter uuids when listing devices 2016-01-21 10:26:13 -04:00
fb1ef0df63 Promote shorter uuids in all examples 2016-01-21 10:23:40 -04:00
4489b1daa0 Merge pull request #299 from resin-io/misc/remove-tests
Remove tests tasks and CI configuration
2016-01-21 10:18:53 -04:00
96f0b5fbd6 Remove tests tasks and CI configuration
We don't have any tests in this repository
2016-01-21 10:16:54 -04:00
f6897ad41f Merge pull request #298 from resin-io/upgrade/sdk
Upgrade Resin SDK to v5.0.1
2016-01-21 10:14:56 -04:00
69e031da28 Upgrade Resin SDK to v5.0.1
This breaking change doesn't affect the CLI in any way, so we can
upgrade directly.
2016-01-21 10:13:15 -04:00
c61a7ef94a Merge pull request #296 from resin-io/ci/notifications
Improve Travis and Appveyor notifications
2016-01-18 11:32:48 -04:00
65bc22c02c Merge pull request #295 from resin-io/fix/delete-device-resource-on-errors
Remove registered device resource in case of errors in quickstart
2016-01-14 10:58:18 -04:00
1d55ea4dcf Improve Travis and Appveyor notifications 2016-01-14 10:49:11 -04:00
add30b33a1 Remove registered device resource in case of errors in quickstart
A device resource needs to be registered with the API before being able
to create the `config.json` file that goes in a device.

This means thats the device image is configured and written to an
external drive (e.g: SDCard) *after* the device resource registered.

If any of the above operations fail, there will be an unitialized orphan
device living in the selected application that the user will have to
remove himself.
2016-01-14 09:14:45 -04:00
999120e711 Merge pull request #293 from resin-io/doc/faq
Improve FAQ
2016-01-13 09:34:22 -04:00
67ce0f7f2d Merge pull request #294 from resin-io/feat/login-register
Redirect users to signup from login if they don't have an account
2016-01-13 09:34:16 -04:00
4645ad06bc Redirect users to signup from login if they don't have an account 2016-01-13 00:25:31 -04:00
612437ae58 Improvee FAQ with more frequent questions 2016-01-13 00:20:18 -04:00
35a821b904 Improve FAQ staging resin url title 2016-01-13 00:13:07 -04:00
e4359834d6 Rename README documentation section to FAQ 2016-01-13 00:12:04 -04:00
ef7e39450c Merge pull request #292 from resin-io/feat/ux-improvements
UX CLI improvements
2016-01-12 14:28:47 -04:00
c3a5998d5c Reuse messages 2016-01-12 10:45:32 -04:00
78ab2af8ba Print verbose help in resin help command 2016-01-12 10:39:29 -04:00
11354de596 Print an informative message after successful login 2016-01-12 10:30:56 -04:00
86cac606e4 Add Resin.io ASCII art in login 2016-01-12 10:23:46 -04:00
9b052c9aa5 Handle authentication in quickstart
If the user is not logged in, make quickstart prompt for authentication
automatically.
2016-01-12 10:12:44 -04:00
8d709aea7d Implement purely interactive login command
The new login command interactively asks the user if he wants to login
using web/credentials/token.
2016-01-12 09:08:03 -04:00
70ea8dd1a3 Redirect users to GitHub and Gitter in case of errors
Users will ge a better experience by knowing exactly where to go for
help if things go wrong.
2016-01-12 08:31:40 -04:00
11c0d2a847 Merge pull request #289 from resin-io/help/device-init-primary
Don't make `device init` a primary command
2016-01-12 08:08:38 -04:00
301b8a6ba3 Merge pull request #290 from resin-io/doc/quickstart-sudo
Stop instructing users to run quickstart as root
2016-01-12 08:08:34 -04:00
acb0aa445c Merge pull request #291 from resin-io/doc/support-gitter
Point users to Gitter in Support README section
2016-01-12 08:08:29 -04:00
a90d578c85 Point users to Gitter in Support README section 2016-01-11 16:04:22 -04:00
d859228aa9 Stop instructing users to run quickstart as root
Elevation is asked in specific steps automatically.
2016-01-11 16:01:40 -04:00
2be105d329 Don't make device init a primary command
Unlikely that a user will run this directly having the more high level
`quickstart`.
2016-01-11 16:00:07 -04:00
6d48fcfd6f Merge pull request #288 from resin-io/misc/build
Build CoffeeScript files with LICENSE additions
2016-01-11 15:59:06 -04:00
551a315432 Build CoffeeScript files with LICENSE additions 2016-01-11 15:58:35 -04:00
351dfdb892 Merge pull request #287 from resin-io/feat/gitter-badge
Add Gitter badge
2016-01-11 12:19:06 -04:00
dc6727fbf1 Add Gitter badge 2016-01-11 11:28:33 -04:00
af88e48c39 Merge pull request #285 from resin-io/doc/license
Change license to Apache 2.0
2016-01-04 10:25:33 -04:00
9cfce68489 Change license to Apache 2.0 2016-01-03 23:58:51 -04:00
8393ff647c Merge pull request #283 from resin-io/doc/cli-staging
Document how to point the CLI to staging
2016-01-01 02:19:43 -04:00
63122a5f51 Merge pull request #282 from resin-io/feat/credential-auth
Add optional credential-based authentication
2015-12-31 22:02:31 -04:00
0ad4598575 Document how to point the CLI to staging 2015-12-31 22:00:39 -04:00
b71c28cec0 Add optional credential-based authentication 2015-12-12 00:11:04 -04:00
b0ab23dad4 v2.5.0 2015-12-11 21:30:02 -04:00
de9297c351 Merge pull request #281 from resin-io/feat/logs-timestamp
Add timestamp to logs lines
2015-12-11 21:26:21 -04:00
26e3cb7957 Add timestamp to logs lines 2015-12-11 20:28:29 -04:00
bb78a3ca09 Merge pull request #280 from resin-io/feat/lazy-loading
Lazy load command actions dependencies
2015-12-07 16:21:32 -03:00
210680c9c9 Lazy load command actions dependencies
In my system (MBPr 13), printing the current version takes over 2
seconds:

```sh
$ time ./bin/resin version
2.4.0
./bin/resin version  1.37s user 0.19s system 73% cpu 2.130 total
```

The CLI takes almost all of these time to parse the dependency tree
before returning control over the actually called command.

To mitigate this problem, we only require the NPM dependencies a command
requires when executing such command, and thus prevent dependencies from
being required and parsed unnecessary.

After this improvement, printing the original example (`resin version`)
returns in less than a second (2x improvement):

```sh
$ time ./bin/resin version
2.4.0
./bin/resin version  0.88s user 0.09s system 102% cpu 0.938 total
```
2015-12-07 11:48:54 -03:00
6810eb31fd Merge pull request #279 from resin-io/jviotti/feat/npmignore
Add npmignore
2015-12-04 11:02:01 -04:00
f5b6df483d Merge pull request #278 from resin-io/jviotti/doc/readme-inst
Add --production to installation instructions
2015-12-04 10:55:56 -04:00
7d0da7adc0 Merge pull request #277 from resin-io/jviotti/feat/token-login
Support for login in with token
2015-12-04 10:55:39 -04:00
785f2b4ef5 Add npmignore 2015-12-04 08:51:28 -04:00
b668a8c7d0 Add --production to installation instructions 2015-12-04 08:31:39 -04:00
2e247faae4 Merge pull request #276 from resin-io/jviotti/feat/quickstart-after-instructions
Add helpful instructions after quickstart
2015-12-03 10:24:13 -04:00
3997a61b78 Support for login in with token
This is useful in the scenario when the user is using the CLI in an
environment in which he/she doesn't have access to a web browser, like a
headless server or a Vagrant development environment.
2015-12-03 10:22:22 -04:00
8ef27f0525 Add helpful instructions after quickstart 2015-12-02 16:28:32 -04:00
20855be968 Merge pull request #275 from resin-io/jviotti/feat/sudo-explain
Explain why we need the computer password on device init
2015-12-02 15:59:27 -04:00
800d13e3cd Merge pull request #274 from resin-io/jviotti/feat/id-devices
Show id in devices command
2015-12-02 15:59:21 -04:00
abc8399260 Explain why we need the computer password on device init 2015-12-02 11:15:20 -04:00
9ad2ba1131 Show id in devices command 2015-12-02 09:06:41 -04:00
0f8d6a98e3 v2.4.0 2015-12-01 14:43:53 -04:00
81af8c74b4 Merge pull request #273 from resin-io/jviotti/update/doc
Update generated documentation
2015-12-01 14:41:29 -04:00
16ab74294f Update generated documentation 2015-12-01 12:56:53 -04:00
f8bcc9d1ea Merge pull request #272 from resin-io/jviotti/feature/automatic-login-exchange
Perform automatic token exchange with the dashboard
2015-12-01 12:56:07 -04:00
9a89e3c3ca Perform automatic token exchange with the dashboard 2015-11-25 09:25:08 -04:00
359c37f259 Merge pull request #270 from resin-io/jviotti/fix/remove-debug-download-msg
Simplify download output message
2015-11-24 11:07:40 -04:00
1ba5697986 Merge pull request #271 from resin-io/jviotti/dependencies/remove-unused
Remove unused dependencies
2015-11-24 11:07:21 -04:00
647ed1e7aa Remove unused dependencies 2015-11-24 00:06:44 -04:00
b881e23c1c Merge pull request #269 from resin-io/jviotti/feature/device-type-in-app-list
Show device types when selecting applications
2015-11-23 23:39:34 -04:00
ae8621dc81 Simplify download output message
The message displayed the output of the download, which was mainly used
for debugging purposes when developing `device init` and `quickstart`.
2015-11-23 23:38:28 -04:00
e08c3752f9 Show device types when selecting applications
Some CLI commans prompt to select an existing application, presending a
dropdown with all the application names, however it's hard to remember
which application belon to which device type, which makes it easier to
select the wrong application.
2015-11-23 09:23:08 -04:00
65646d1206 v2.3.0 2015-11-20 13:01:58 -04:00
e42d3e8c4c Merge pull request #266 from resin-io/jviotti/feature/resin-url-info
Clarify resin url on auth and whoami
2015-11-18 16:15:46 -04:00
a4642f6184 Clarify resin url on auth and whoami
When you change the `resinUrl` config from time to time it can be
confusing to remember where you're logging in, or in which host you're
in.

Currently I have to check the configuration files/environment variables
manually or run `resin settings`.

This PR prints the detected resin url on `resin login` and `resin
whoami` so it's always clear where you are.
2015-11-16 10:11:08 -04:00
038c871911 Merge pull request #265 from resin-io/jviotti/feature/settings
Implement settings command
2015-11-16 09:03:20 -04:00
f52dd2976f Implement settings command
This command allows the user to list all detected settings.
2015-11-15 22:08:02 -04:00
d079a57da4 Merge pull request #264 from resin-io/jviotti/fix/devices-uuid
Show uuid in devices command
2015-11-13 15:33:17 -04:00
43697a3476 Show uuid in devices command
The command to get information about a device, `resin device` requires a
`uuid` as a parameter. Given that we don't show uuids in `resin
devices`, the user has no way to know what uuid to pass to get extra
information.

We also remove some non very used information columns from `resin
devices` to make space for the uuid.
2015-11-13 14:06:55 -04:00
b893bd1e39 Merge pull request #262 from resin-io/jviotti/feature/windows-elevation
Handle Windows permissions elevation automatically
2015-11-12 14:05:44 -04:00
c3b5a768e1 Handle Windows permissions elevation automatically 2015-11-12 13:30:48 -04:00
111ea44b40 Resin CLI v2.2.0 2015-11-12 08:33:51 -04:00
d1b25c17b6 Merge pull request #261 from resin-io/jviotti/feature/move-device
Implement device move command
2015-11-12 08:32:55 -04:00
ba318f2939 Implement device move command
This command allows to user to move a device to another application he
owns.
2015-11-12 07:47:16 -04:00
7ac0291c53 Merge pull request #260 from resin-io/jviotti/102/feature/device-reconfigure
Implement config reconfigure command
2015-11-11 10:59:31 -04:00
f64676ab98 Implement config reconfigure command
This command allows the user to reconfigure an already provisioned
device.

Fixes: https://github.com/resin-io/resin-cli/issues/102
2015-11-11 10:38:45 -04:00
d522cbe1ca Merge pull request #259 from resin-io/jviotti/refactor/resin-config-json
Make use of resin-config-json for config commands
2015-11-11 10:27:24 -04:00
4fc7a4e436 Make use of resin-config-json for config commands
This module encapsulates the low level details of `config.json` I/O and
tests them extensively.

See: https://github.com/resin-io/resin-config-json
2015-11-11 10:04:46 -04:00
79f2b4f0d5 Merge pull request #258 from resin-io/jviotti/doc/regenerate
Regenerate documentation
2015-11-11 09:17:52 -04:00
880a7b1e25 Regenerate documentation 2015-11-11 08:45:38 -04:00
89c5bb3080 Resin CLI v2.1.0 2015-11-11 08:31:32 -04:00
a71fb8ca4d Merge pull request #257 from resin-io/jviotti/feature/config-json-write
Implement config write command
2015-11-11 08:29:10 -04:00
3b35aed3bf Implement config write command
This command allows the user to overwrite properties of the config.json
file.
2015-11-10 14:28:18 -04:00
0bc3b9460f Merge pull request #256 from resin-io/jviotti/feature/read-config
Implement config read command
2015-11-10 13:08:54 -04:00
5509a3e9fd Implement config read command
This command is used to read a config.json from a provisioned device
2015-11-10 12:53:34 -04:00
f84d0d0980 Merge pull request #253 from resin-io/jviotti/refactor/sudo-president
Use president to provide sudo functionality
2015-10-27 07:58:17 -04:00
c866f6e46c Use president to provide sudo functionality 2015-10-26 09:47:49 -04:00
3192f9d2ef Merge pull request #252 from resin-io/jviotti/fix/239/sudo-explain
Clarify the need of computer password during sudo
2015-10-26 09:42:26 -04:00
de83a06db8 Clarify the need of computer password during sudo
Since we only prompt "Password:", it might be confusing for some users
that think they have to enter their Resin.io password instead.

Fixes: https://github.com/resin-io/resin-cli/issues/239
2015-10-26 08:49:41 -04:00
9260c8dce2 Resin CLI v2.0.1 2015-10-26 08:46:16 -04:00
bea5f22732 Merge pull request #251 from resin-io/jviotti/fix/stream-wait-sudo
Refer to the correct waiting stream function
2015-10-26 08:45:14 -04:00
363f12f81b Refer to the correct waiting stream function
We recently changed to using `rindle`, however looks like we forgot
to replace this particular instance.
2015-10-26 08:34:07 -04:00
1cbd33679f Resin CLI v2.0.0 2015-10-26 08:08:20 -04:00
e962371b59 Merge pull request #248 from resin-io/jviotti/remove/associate
Remove app associate command
2015-10-21 14:13:28 -04:00
77a3d4d6f6 Merge pull request #249 from resin-io/jviotti/fix/validation-module
Fix validation module require typo
2015-10-21 14:13:21 -04:00
fc5fe6cf68 Fix validation module require typo 2015-10-21 13:28:51 -04:00
f921488e8c Remove app associate command 2015-10-21 13:25:22 -04:00
8fe08642f5 Merge pull request #245 from resin-io/jviotti/feature/simplify-quickstart
Remove project directory creation in quickstart
2015-10-21 13:20:35 -04:00
822632718f Merge pull request #247 from resin-io/jvioti/refactor/os
Remove unused getOperatingSystem function
2015-10-21 11:43:19 -04:00
f66cd00646 Remove project directory creation in quickstart
The last part of `quickstart` feels weird. By consensus, we remove the
part that attempts to create a project directory and leave that step to
the user.
2015-10-21 11:18:29 -04:00
5498e45a35 Merge pull request #246 from resin-io/jviotti/refactor/rindle
Use rindle instead of custom waitStream
2015-10-21 11:15:12 -04:00
38479b3191 Merge pull request #244 from resin-io/jviotti/refactor/unused-npm
Remove unused npm dependency
2015-10-21 10:22:09 -04:00
965fd8fc19 Remove unused getOperatingSystem function 2015-10-21 10:20:38 -04:00
7a4f551a47 Use rindle instead of custom waitStream 2015-10-21 10:17:10 -04:00
303fd74012 Merge pull request #243 from resin-io/jviotti/feature/validation
Refactor validation to a single place
2015-10-21 10:11:46 -04:00
6a7d1b0c70 Remove unused npm dependency 2015-10-21 10:02:08 -04:00
c1e6a28640 Refactor validation to a single place 2015-10-21 09:37:25 -04:00
ec72f93480 Merge pull request #242 from resin-io/jviotti/feature/us-pw-login
Implement user/password login with 2FA support
2015-10-21 09:32:17 -04:00
8913fb515b Implement user/password login with 2FA support 2015-10-21 08:28:20 -04:00
6f91ff898f Merge pull request #241 from resin-io/jviotti/doc/drive
Regenerate os initialize documentation
2015-10-20 10:22:40 -04:00
aa949103f6 Merge pull request #240 from resin-io/jviotti/feature/device-init-advanced
Allow advanced option in device init
2015-10-20 09:47:04 -04:00
14bd4ad74e Regenerate os initialize documentation 2015-10-20 09:17:48 -04:00
f2507daa09 Allow advanced option in device init
This option is inherited to `os configure`.
2015-10-20 09:16:56 -04:00
ff81c1e514 Merge pull request #234 from resin-io/jviotti/feature/advanced-options
Ignore advanced configuration questions by default
2015-10-20 09:12:38 -04:00
7ce92b47a0 Merge pull request #238 from resin-io/jviotti/cleanup/dependencies
Remove unused resin-device-config
2015-10-19 16:04:50 -04:00
bde5cc65da Merge pull request #235 from resin-io/jviotti/fix/device-register-help
Load device info after all other device commands
2015-10-19 15:51:15 -04:00
ec9347c3a4 Merge pull request #237 from resin-io/jviotti/fix/hound
Change java_script to javascript in hound config
2015-10-19 15:51:07 -04:00
ceb8dada1d Remove unused resin-device-config 2015-10-19 15:26:09 -04:00
f4186acf80 Merge pull request #236 from resin-io/jviotti/feature/device-register-uuid
Allow passing a custom uuid to device register
2015-10-19 15:17:25 -04:00
05ce54eac1 Change java_script to javascript in hound config
They seemed to change this recently.
2015-10-19 14:21:10 -04:00
d28ecf3230 Allow passing a custom uuid to device register 2015-10-19 14:16:47 -04:00
8562f723c5 Load device info after all other device commands
This command obscures help pages for all device commands registered
afterwards since it's a common prefix for all of them.
2015-10-19 14:14:04 -04:00
f6d2043747 Merge pull request #232 from resin-io/jviotti/fix/app-create-validation
Require application name to have at least 4 characters
2015-10-19 14:06:43 -04:00
485818f3b5 Merge pull request #233 from resin-io/jviotti/fix/app-create-type
Fix --type option taking no effect in app create
2015-10-19 14:03:59 -04:00
ec28bd9c9e Ignore advanced configuration questions by default
The advanced questions can be enabled by passing `--advanced` in `os
configure`.
2015-10-19 14:02:57 -04:00
ad68dcf692 Fix --type option taking no effect in app create 2015-10-19 13:07:23 -04:00
0b7e2a2c8c Require application name to have at least 4 characters
We get a weird error message from pine otherwise:

	ResinRequestError: Request error: It is necessary that each app name
	that is of a user (Auth), has a Length (Type) that is greater than or
	equal to 4.
2015-10-19 10:56:02 -04:00
b6ebd0631a Merge pull request #229 from resin-io/jviotti/feature/os-initialize-type-option
Take device type as an option in os initialize
2015-10-15 09:59:44 -04:00
7ace4bdfa6 Merge pull request #230 from resin-io/jviotti/fix/update
Improve the way the update notifier is shown
2015-10-15 09:43:20 -04:00
1cfbd4197d Improve the way the update notifier is shown
Current has the following problems:

- Our custom message gets printed even if the notifier doesn't contain
an update.

- The notifier box is deferred, therefore it's printed at the end of the
command. Since our custom message is printed at the beginning, it makes
no sense at all.
2015-10-15 09:18:45 -04:00
b2425d2c0e Take device type as an option in os initialize 2015-10-15 09:11:38 -04:00
0ba914236a Merge pull request #228 from resin-io/jviotti/feature/os-initialize-drive
Support drive option in os initialize
2015-10-15 08:49:26 -04:00
71ee0a6cf7 Support drive option in os initialize
This allows the user to bypass the drive selection dialog.

This option can be used along with `--yes` to make the command
completely non-interactive. For example:

	$ resin os initialize rpi.img 'raspberry-pi' --drive /dev/disk2 --yes
2015-10-15 08:14:35 -04:00
4326ad4d9c Merge pull request #227 from resin-io/jviotti/fix/os-initialise-yes
Add missing `yes` option to `os initialize`
2015-10-15 07:44:33 -04:00
055bac6ff4 Merge pull request #226 from resin-io/jviotti/feature/223/update-sudo
Clarify the need for admin privileges on update
2015-10-15 07:40:15 -04:00
58713dc291 Add missing yes option to os initialize
This option is tried to be used within the command, but is not defined
as a formal Capitano option.
2015-10-14 17:49:27 -04:00
adf4aef517 Clarify the need for admin privileges on update
Fixes: https://github.com/resin-io/resin-cli/issues/223
2015-10-14 13:45:08 -04:00
a17bb2cc09 Merge pull request #220 from resin-io/jviotti/changelog/spacing
Remove spaces between lists in CHANGELOG
2015-10-13 13:04:08 -04:00
48d620f7cd Remove spaces between lists in CHANGELOG
GitHub displays the lists in a weird way otherwise.
2015-10-13 12:39:43 -04:00
fa7b104762 Resin CLI v1.1.0 2015-10-13 12:37:50 -04:00
faf2fa167f Merge pull request #219 from resin-io/jviotti/doc/troubleshoot
Remove outdated troubleshooting information
2015-10-13 12:17:46 -04:00
ad1e68427c Remove outdated troubleshooting information 2015-10-13 11:46:21 -04:00
e9147f0f6f Merge pull request #218 from resin-io/jviotti/fix/windows-edison
Avoid _.ary in temporal path disposer
2015-10-12 18:28:30 -04:00
cddf630907 Avoid _.ary in temporal path disposer
For some reason fails with a weird Bluebird error on Windows
2015-10-12 18:12:58 -04:00
494a286cae Merge pull request #217 from resin-io/jviotti/upgrade/sdk
Upgrade SDK to v3.0.0
2015-10-12 09:03:59 -04:00
e5e871ddcd Upgrade SDK to v3.0.0
Breaking changes in this version:

- `resin.models.device.generateUUID()` is now async.
2015-10-12 08:34:22 -04:00
5a3166e3de Merge pull request #215 from resin-io/jviotti/fix/await-wrap
Shorten the length of await device message
2015-10-07 12:41:57 -04:00
3149464c7a Shorten the length of await device message
If the spinner message doesn't fit in your terminal, each spinner
position will be printed in different lines.

We mitigate this by dramatically shortenning the message.
2015-10-07 11:38:59 -04:00
97d9b7816f Merge pull request #214 from resin-io/jviotti/feature/plugin-warn-red
Print plugin warnings in red as other errors
2015-10-06 19:18:33 -04:00
ec77437080 Print plugin warnings in red as other errors
For the sake of consistency.
2015-10-06 18:51:17 -04:00
d65882639a Merge pull request #211 from resin-io/jviotti/feature/101/help-topics
Separate general help per topic relevance
2015-10-02 09:12:58 -04:00
f8470287c1 Separate general help per topic relevance
Only list primary commands by default, unless a `--verbose` option is
passed to list the additional ones.

Fixes: https://github.com/resin-io/resin-cli/issues/101
2015-10-02 08:50:32 -04:00
3cc41ed62a Merge pull request #212 from resin-io/jviotti/fix/109/root-owner
Call os initialize as an elevated process
2015-10-01 14:08:19 -04:00
445e37ccaf Call os initialize as an elevated process
Currently, the fact that `os initialize` requires elevated permissions
forced us to require calling commands that reuse it, such as `device
init` and `quickstart` with administrator permissions as well.

This ended up causing issues like saving images in the cache that belong
to root, or initializing git repositories that requires `sudo` to
commit.

The solution is to call `os initialize` as a child process preppending
`sudo` within `device init`.

Fixes: https://github.com/resin-io/resin-cli/issues/109
2015-10-01 13:07:53 -04:00
ed6427c541 Merge pull request #210 from resin-io/jviotti/feature/os-init-type
Make os initialize take a device type instead of a uuid
2015-09-30 15:59:58 -04:00
90be01b05d Make os initialize take a device type instead of a uuid 2015-09-30 14:31:23 -04:00
1124293b9a Merge pull request #209 from resin-io/jviotti/doc/update
Regenerate documentation
2015-09-30 14:30:43 -04:00
0e804fdfd8 Regenerate documentation 2015-09-30 12:07:44 -04:00
0443a35f2b Merge pull request #207 from resin-io/jviotti/fix/promise-using
Fix incorrect Promise.using syntax
2015-09-30 12:06:51 -04:00
3a148217e0 Merge pull request #208 from resin-io/jviotti/fix/manifest-device-type
Send device type correctly to getManifestBySlug
2015-09-30 12:06:45 -04:00
79d1892b66 Send device type correctly to getManifestBySlug
Currently, we we're sending the wholea device object to
`getManifestBySlug`, which ended up in an unsupported device error.
2015-09-30 11:38:34 -04:00
0e06ac464f Fix incorrect Promise.using syntax
`Promise.using` takes the function that acts on the resource as the
second argument, instead of as `.then()`.
2015-09-30 11:37:27 -04:00
5ae83d8337 Merge pull request #206 from resin-io/jviotti/fix/uncompress-os-download
Uncompress zip packages in os download
2015-09-30 10:48:36 -04:00
8694ee2c59 Merge pull request #205 from resin-io/jviotti/feature/rimraf-file-disposer
Use rimraf for deleting os temporary files
2015-09-30 10:31:46 -04:00
8234f7675a Uncompress zip packages in os download
When downloading an operating system image, if the image is a zip
package, uncompress it automatically.
2015-09-30 10:16:24 -04:00
15cb0c4889 Use rimraf for deleting os temporary files
We already use `rimraf` for deleting os temporary directories, however
there are a few benefits of using it for files as well:

- Simplicity. We avoid having to check if a path is a file or directory.
- `rimraf` attempts to workaround the known Windows issues of anti
viruses not closing files. Described in more detail here: https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md#i-get-ebusy-errors-after-initializing-a-device-even-as-administrator-on-windows
2015-09-30 10:06:08 -04:00
a3ebd9827f Merge pull request #204 from resin-io/jviotti/fix/console.info
Use console.info in os download
2015-09-30 10:05:19 -04:00
30d84f015a Merge pull request #199 from resin-io/jviotti/feature/197/device-init-apps
Prompt for select application if running device init with no arguments
2015-09-29 15:31:59 -04:00
686414b03d Merge pull request #198 from resin-io/jviotti/feature/temp-disposer
Use Promise.disposer() to make sure temp files are deleted
2015-09-29 15:25:09 -04:00
6377618c12 Use console.info in os download
`console.info` calls can be quieted by the `--quiet` option.
2015-09-29 15:15:39 -04:00
f17e9c97b8 Prompt for select application if running device init with no arguments
Currently, if `device init` was ran without an application argument, we
attempted to get the application name from the current directory, given
it was a git repository.

This approach led to confusions from time to time, so now we prompt the
user to select one of it's own applications from a dropdown instead of
checking the current directory in this edge case.

Fixes: https://github.com/resin-io/resin-cli/issues/197
2015-09-29 15:10:59 -04:00
21fcdfaff6 Use Promise.disposer() to make sure temp files are deleted 2015-09-29 15:08:24 -04:00
4072edcced Merge pull request #203 from resin-io/jviotti/feature/os-initialize
Implement os initialize command
2015-09-29 14:59:59 -04:00
d704c10197 Implement os initialize command
This command initialized an operating system image with a disk device.
2015-09-29 14:52:34 -04:00
dea2a7f055 Merge pull request #202 from resin-io/jviotti/feature/device-register
Implement device register command
2015-09-29 14:41:03 -04:00
7e6eb4b9e4 Implement device register command
This command registers a new device with the passed application,
returning the new device uuid.
2015-09-29 14:33:31 -04:00
61474fba5c Merge pull request #201 from resin-io/jviotti/feature/os-configure
Implement os configure
2015-09-29 14:26:03 -04:00
42256384be Implement os configure
This command, given a path to an image and a device uuid, perform
configuration based on the resin-device-type manifests.
2015-09-29 13:47:10 -04:00
e25f232fe5 Merge pull request #200 from resin-io/jviotti/feature/os-download
Implement os download command
2015-09-29 13:11:24 -04:00
f6d8f12ba2 Implement os download command
This command download an unconfigured image to both the cache and to the
specified location by the `--output` option.
2015-09-29 13:03:14 -04:00
d2b9e6fd8c Merge branch 'jviotti/doc/anti-virus-ebusy' 2015-09-21 09:28:13 -04:00
cf7effacc0 Document anti virus and EBUSY errors on Windows 2015-09-21 09:27:18 -04:00
1eb7aba293 Merge branch 'master' of https://github.com/resin-io/resin-cli 2015-09-21 09:25:57 -04:00
21e916679c Update author email to @resin.io 2015-09-21 09:15:27 -04:00
d574743c21 Merge pull request #195 from resin-io/jviotti/docs/cygwin-to-troubleshooting
Move Cygwin caveat from README to TROUBLESHOOTING
2015-09-21 08:57:39 -04:00
d8a2b82662 Move Cygwin caveat from README to TROUBLESHOOTING 2015-09-21 08:51:54 -04:00
f4ebd890df Merge pull request #194 from resin-io/jviotti/doc/troubleshooting
Add Troubleshooting guide
2015-09-21 08:47:14 -04:00
b0be5de83a Add Troubleshooting guide
This guide described common Resin CLI issues and how to fix them.
2015-09-21 08:32:11 -04:00
ba21ddd010 Merge pull request #192 from resin-io/jviotti/doc/readme
Improve README.md
2015-09-21 08:01:44 -04:00
eecd7a0fd8 Improve README.md
- Make user oriented instead of developer oriented.
- Remove deprecated information.
- Add Requisites section.
- Add Getting Started section.
- Add Support section.
- Add License section.
2015-09-21 07:56:24 -04:00
eb4c2f62a7 Resin CLI v1.0.0 2015-09-11 21:21:44 +03:00
dc1e5e6512 Merge pull request #190 from resin-io/jviotti/message/await
Improve device awaiting message
2015-09-11 19:19:14 +03:00
adc0b183cd Improve device awaiting message
Current message sounds too robotic.
2015-09-11 19:13:30 +03:00
828b4f73d1 Fix selecting existing application in quickstart 2015-09-11 18:30:30 +03:00
82a0761f49 Merge pull request #189 from resin-io/jviotti/feature/error-highlight
Highlight errors in red
2015-09-11 14:59:14 +03:00
904b9f07fb Highlight errors in red
- Move error translation logic to resin-io/resin-cli-errors.
- Force `process.exit()`.
2015-09-11 14:47:38 +03:00
3c0acaa7da Merge pull request #188 from resin-io/jviotti/feature/device-specs
Implement device specs. Fix #99
2015-09-11 13:10:44 +03:00
64c8420c9d Implement device specs. Fix #99
Support for all devices. Tested in the following ones:

- Intel Edison.
- Raspberry Pi 2.
- Parallella.
2015-09-11 13:02:59 +03:00
26e3dc1aa7 Merge pull request #187 from resin-io/jviotti/feature/ignore-resinrc
Add resinrc.yml to gitignore
2015-09-08 09:31:40 +03:00
3bc22a7b7c Merge pull request #186 from resin-io/jviotti/refactor/settings
Use settings from the SDK during login
2015-09-08 09:16:05 +03:00
7e5b2695fe Add resinrc.yml to gitignore
Some modules have an use case for shipping the configuration file with
the project, however this is not the case for the CLI.
2015-09-08 09:07:48 +03:00
79afa79fd9 Use settings from the SDK during login
This enforces all clients to use the Resin Settings Client version that
the SDK provides, reducing incompatibilities caused by different modules
requiring different Resin Settings Client versions.
2015-09-08 09:06:03 +03:00
cdaaddb826 Merge pull request #185 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.7.2
2015-09-07 11:45:25 +03:00
ec5f6a7cd8 Upgrade Resin SDK to v2.7.2 2015-09-07 11:25:01 +03:00
72f34031a9 Merge pull request #184 from resin-io/jviotti/fix/token-auth
Check token validity against the API when login
2015-09-05 20:48:22 +03:00
dc257b5cab Check token validity against the API when login
Consider the following case:

The SDK is configured to point to staging, but the user passes a token
from production, or viceversa. Since the token is valid in a sense that
is valid JWT and contains real data, the CLI will report as a success.

The user will then get Unauthorized errors when using the API.
2015-09-05 20:17:34 +03:00
4bdcd3d2ee Merge pull request #180 from resin-io/issue_#103
Resin CLI Events integration. Fix #103
2015-09-05 20:11:52 +03:00
b0650530cc Resin CLI Events integration. Fix #103 2015-09-05 19:15:31 +03:00
454669ada2 Merge pull request #183 from resin-io/jviotti/182/docs/update
Update documentation
2015-09-01 13:09:42 -04:00
a65975596e Merge pull request #181 from resin-io/reword-example
Replace device name with uuid in env-variables command example
2015-09-01 13:03:27 -04:00
91f878cfc0 Update documentation
- There were changes to commands.
- Regenerates `login` documentation with production url.

Fixes: https://github.com/resin-io/resin-cli/issues/182
2015-09-01 13:01:47 -04:00
8c3e832cdc Replace device name with uuid in env-variables command example 2015-09-01 00:56:01 +03:00
a5c670902d Merge pull request #179 from resin-io/jviotti/appveyor/cache
Add Appveyor cache support
2015-08-28 08:42:57 -04:00
14be1d5f9f Add Appveyor cache support 2015-08-28 08:22:26 -04:00
41d6d5c670 Merge pull request #178 from resin-io/jviotti/refactor/quickstart
Refactor quickstart
2015-08-27 10:13:56 -04:00
a090e6c21d Refactor quickstart
- Use promises.
- Move some logic to `helpers`.
- Inline `device await` command.
2015-08-27 10:01:33 -04:00
d78c1ffa47 Merge pull request #176 from resin-io/jviotti/cleanup/compiled-files
Remove orphaned files from build/
2015-08-27 09:03:38 -04:00
5808d20d88 Merge pull request #177 from resin-io/modify-capitanodoc
Update capitanodoc.json
2015-08-27 15:45:05 +03:00
7f4065e3da Update capitanodoc.json 2015-08-27 15:30:38 +03:00
07daa51051 Remove orphaned files from build/
Some files were deleted from `lib/` but still live in `build/`. More
specifically:

- `build/actions/update.js`.
- `build/data/`.
2015-08-27 08:29:22 -04:00
d10d4ce185 Merge pull request #174 from resin-io/jviotti/feature/confirm-abortion
Add `Aborted` error message when not accepting a confirmation
2015-08-24 08:47:29 -04:00
7f763e7881 Merge pull request #175 from resin-io/jviotti/fix/update-root
Don't check for available updates when running as root
2015-08-24 07:41:56 -04:00
5de0f66d7a Don't check for available updates when running as root
`update-notifier` persist its update check results in a file, which is
then read when running again the application.

If this file gets written when the application is being run as root, we
get ugly EPERM issues.
2015-08-20 16:54:22 -04:00
354921ca92 Add Aborted error message when not accepting a confirmation
This prevents a lot of duplicate code to check for confirmation status
and exit from the current action.
2015-08-20 16:16:20 -04:00
fb28cc7d2f Merge pull request #173 from resin-io/jviotti/upgrade/form
Upgrade Resin CLI Form to v1.2.1
2015-08-20 16:11:17 -04:00
7cf89a9bf6 Merge pull request #172 from resin-io/jviotti/cleanup/readme
Remove Man pages section from README.md
2015-08-20 16:11:09 -04:00
a3cbc549d8 Upgrade Resin CLI Form to v1.2.1
This version contains a fix for a bug that prevented `when` properties
from working as expected.
2015-08-20 15:55:57 -04:00
1c48328347 Remove Man pages section from README.md
Man pages are not longer being produced.
2015-08-20 12:04:11 -04:00
9b69fe3c3c Merge pull request #171 from resin-io/jviotti/upgrade/cli-form
Update Resin CLI Form to v1.2.0
2015-08-20 12:02:26 -04:00
dc513a08f6 Update Resin CLI Form to v1.2.0
This version includes support for the `drive` input type.
2015-08-20 11:55:53 -04:00
fcc44949a7 Merge pull request #169 from resin-io/jviotti/refactor/plugins
Upgrade Nplugm to v3.0.0
2015-08-19 12:49:32 -04:00
006764af66 Merge pull request #170 from resin-io/jviotti/cleanup-resin-write
Remove unused resin-write bin script
2015-08-19 12:49:25 -04:00
b879d3f9ea Remove unused resin-write bin script
This script was used along with windosu as a workaround to call `device
init` with elevated permissions.

Since Windows elevation is not used anymore for now, this script can be
removed.
2015-08-19 11:30:48 -04:00
7f4863da86 Upgrade Nplugm to v3.0.0
This new version supports promises and contains speed improvements.
2015-08-19 11:27:28 -04:00
ba6f50d171 Merge pull request #168 from resin-io/jviotti/cleanup/plugins
Remove plugins manipulation commands
2015-08-19 11:14:35 -04:00
a803d4f646 Remove plugins manipulation commands
Since we're now forcing users to rely on `npm` directly for updates, we
can also get rid of plugin commands that attempt to
install/update/remove using npm programatically and require users to use
`npm` directly as well.

This commit removes the following commands:

- `plugins`
- `plugin install`
- `plugin update`
- `plugin remove`

Despite plugin related commands being removed, *the functionality that
scans for plugins and registers them remains intact*.
2015-08-19 10:57:42 -04:00
85d940df66 Merge pull request #165 from resin-io/jviotti/feature/update-notifier
Notify the user if there is an available update
2015-08-19 07:56:40 -04:00
6f9535ca34 Merge pull request #167 from resin-io/issue-166
Display msg when app/device does not have env variables. Fix #166.
2015-08-18 18:17:21 +03:00
019e2ac357 Display msg when app/device does not have env variables. Fix #166 2015-08-18 18:12:08 +03:00
433916e18a Merge pull request #164 from resin-io/issue-90
Add message informing the user about potential delay in system img initialization. Fix #90.
2015-08-18 16:04:44 +03:00
f19588032f Notify the user if there is an available update
For this we use the `update-notifier` module with its default settings.

This module will print a nice banner prompting the user to run the
corresponding npm command to update.
2015-08-18 08:53:06 -04:00
0595452c3d Add message informing the user about potential delay in system img initialization. Fix #90. 2015-08-18 15:43:52 +03:00
d6305df48e Merge pull request #163 from resin-io/issue-108
Reword ending message in quickstart. Fix #108
2015-08-18 15:00:38 +03:00
f7084580b2 Merge pull request #162 from resin-io/issue-106
Reword output during download in device init. Fix #106.
2015-08-18 14:59:08 +03:00
3dd5f5858a Reword ending message in quickstart. Fix #108 2015-08-18 14:34:15 +03:00
02a06e1e7c Reword output during download in device init. Fix #106. 2015-08-18 13:55:04 +03:00
50daf8ef73 Merge pull request #161 from resin-io/jviotti/refactor/env
Refactor env action module to use promises
2015-08-17 10:42:40 -04:00
fd5a34a1c4 Refactor env action module to use promises 2015-08-17 10:32:22 -04:00
51fda13684 Merge pull request #160 from resin-io/jviotti/remove/devices-supported
Remove `devices supported` command
2015-08-17 10:22:10 -04:00
a698b25fda Remove devices supported command
The command is not necessary and unused.
2015-08-17 10:05:36 -04:00
89bd861d8e Merge pull request #159 from resin-io/jviotti/refactor/device
Refactor device actions to use promises
2015-08-17 10:03:57 -04:00
e5b7aae4ae Refactor device actions to use promises 2015-08-17 09:49:59 -04:00
031168ceed Merge pull request #158 from resin-io/jviotti/refactor/keys
Refactor keys action to use promises
2015-08-17 09:46:53 -04:00
09a5788902 Refactor keys action to use promises 2015-08-17 09:32:05 -04:00
713664d103 Merge pull request #157 from resin-io/jviotti/feature/settings-projects-dir
Make use of `projectsDirectory` SDK setting in Quickstart
2015-08-17 09:20:30 -04:00
f63391acf9 Make use of projectsDirectory SDK setting in Quickstart
We were currently building this path ourselves, hardcoding the place of
the resin local per user directory instead of relying on the foundations
that `resin-settings-client` give us.
2015-08-17 09:06:27 -04:00
79ee4302ec Merge pull request #156 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.4.1
2015-08-17 08:59:17 -04:00
9adda22921 Upgrade Resin SDK to v2.4.1
This new version contains fixes for the following issues:

- https://github.com/resin-io/resin-cli/issues/87
- https://github.com/resin-io/resin-cli/issues/120
2015-08-17 08:42:17 -04:00
25311f2a18 Merge pull request #146 from resin-io/jviotti/refactor/auth
Refactor auth actions to use promises
2015-08-17 08:41:19 -04:00
70c060b124 Refactor auth actions to use promises 2015-08-17 08:22:48 -04:00
7a8a3c851b Merge pull request #138 from resin-io/refactor/help
Refactor help module
2015-08-17 08:02:15 -04:00
1096b2d212 Merge pull request #143 from resin-io/jviotti/refactor/app
Refactor app actions to use promises
2015-08-17 08:02:04 -04:00
ee286c5690 Merge pull request #144 from resin-io/jviotti/refactor/note
Refactor note set command to use promises
2015-08-17 08:01:47 -04:00
1da1d2e6fc Merge pull request #152 from resin-io/jviotti/fix/151/ssh-key-list
Print ssh key separately from the information table
2015-08-17 08:01:32 -04:00
64be9f936d Merge pull request #155 from resin-io/jviotti/feature/107/await-spinner
Implement a spinner when awaiting for a device. Fix #107
2015-08-17 08:01:22 -04:00
30f24333c0 Implement a spinner when awaiting for a device. Fix #107
Fixes:

- https://github.com/resin-io/resin-cli/issues/107
2015-08-14 14:35:38 -04:00
30f6a78282 Merge pull request #154 from resin-io/jviotti/fix/device-await
Fix broken device await command
2015-08-14 14:31:00 -04:00
8c9a0e0ff1 Fix broken device await command
There were two issues that prevented this command from working
correctly:

1- `Promise.delay()` is used, but `Promise` was not imported.
2- The following line had incorrect indentation (spaces instead of
		tabs):

		poll().nodeify(done)

Therefore CoffeeScript interpreted that the line had to be executed at
the end of the `poll()` function, causing `poll()` to never be called.
2015-08-14 14:11:49 -04:00
8268bbf700 Merge pull request #150 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.4.0
2015-08-14 12:42:31 -04:00
e712e2f266 Print ssh key separately from the information table
Since the public key string is long, it might wrap to lines below,
causing the table layout to break.

A quick solutio is to print the ssh key after the table.

Fixes:

- https://github.com/resin-io/resin-cli/issues/151
2015-08-14 12:25:55 -04:00
0807b6a2d9 Upgrade Resin SDK to v2.4.0
This release fixes:

- Check if device exists before removing it "resin device rm <uuid>"
	- https://github.com/resin-io/resin-cli/issues/123

- Check if app exists before removing it "resin app rm <appName>"
	- https://github.com/resin-io/resin-cli/issues/114

- Command does not display correct output "resin key <id>"
	- https://github.com/resin-io/resin-cli/issues/112

Since it includes the following PRs:

- https://github.com/resin-io/resin-sdk/pull/103
- https://github.com/resin-io/resin-sdk/pull/107
2015-08-14 12:17:59 -04:00
83382cc8f7 Merge pull request #145 from resin-io/jviotti/fix/logs-help-indentation
Fix logs command help string indentation
2015-08-14 08:27:38 -04:00
8401aaeae2 Merge pull request #149 from resin-io/jviotti/fix/111/email-validation
Validate that email address is valid during signup. Fix #111
2015-08-14 07:58:40 -04:00
abf5740950 Merge pull request #148 from resin-io/jviotti/fix/14/logs-history
Force logs command to exit when not in --tail mode. Fix #14.
2015-08-14 07:58:05 -04:00
606777508d Merge pull request #147 from resin-io/jviotti/cleanup/preferences
Remove preferences command
2015-08-14 07:57:14 -04:00
e9ec6c67b2 Validate that email address is valid during signup. Fix #111
For this we use a third party dependency from npm called `valid-email`
to avoid hardcoding and having to mantain a regular expression.
2015-08-13 15:22:22 -04:00
69566f7fc3 Force logs command to exit when not in --tail mode. Fix #14.
PubNub keeps the process alive after a history query for some reason, so
trying to print the logs history like:

	$ resin logs <uuid>

Will result in the logs being printed correctly, but the process waiting
infinitely without ending.

The workaround consists in forcing `process.exit` to exit the process
with an error code zero.

Caveats:

- This workaround prevents this command to be used programatically.

Issue: https://github.com/resin-io/resin-cli/issues/14
2015-08-13 15:08:16 -04:00
6e4b299c7d Remove preferences command 2015-08-13 15:00:51 -04:00
ef35ebf79d Fix logs command help string indentation
For some reason it was indented a few times unnecesarily.
2015-08-13 14:33:19 -04:00
1bc78edf71 Refactor help module
Main changes:

- Use the `columnify` module to display the commands instead of using
manual parsing.

- Extract logic to create a string representation from an option
signature to Capitano, and reuse here.

See https://github.com/resin-io/capitano/pull/28

Some bugs were caught and fixes during the refactoring:

- In command help, if the command didn't exist, we reused default
Capitanos command not found function which uses `process.exit(1)`. This
was changed to pass a custom error to `done()`, so the command fails
correctly when using programatically.

- General help didn't call `done()` at all, thus causing problems if
using the command programatically someday.
2015-08-13 14:19:07 -04:00
d5204a09f7 Refactor note set command to use promises 2015-08-13 14:17:02 -04:00
4647aa70c0 Implement utils/helpers to abstract common app patterns
- Add helpers.confirm() to abstract the process of asking for
confirmation.
- Add helpers.selectDeviceType() to abstract the form needed to ask for
device types.

The functions on this module are reused by app actions.
2015-08-13 14:04:47 -04:00
2e8ec3ac64 Merge pull request #142 from resin-io/jviotti/cleanup/dependencies
Remove unused dependencies imports from various files
2015-08-13 13:51:22 -04:00
25c6246e9f Refactor app actions to use promises
Use promises instead of `async` internally inside the following
commands:

- app create.
- app remove.
- app associate.
2015-08-13 13:42:49 -04:00
5408938007 Merge pull request #141 from resin-io/jviotti/cleanup/shell-completion
Remove command shell completion script
2015-08-13 13:08:08 -04:00
50cb04b6f7 Remove unused dependencies imports from various files 2015-08-13 13:04:22 -04:00
09fe4b11ad Merge pull request #139 from resin-io/jviotti/feature/drive-widget
Use Visuals drive widget in device init
2015-08-13 12:35:41 -04:00
4157f21e06 Merge pull request #140 from resin-io/jviotti/cleanup/elevate
Remove outdated Windows elevation mechanism
2015-08-13 12:23:26 -04:00
4900230881 Remove command shell completion script
This feature is unused and doesn't provide much value.

In the future, we may include autocompletion support built in the CLI.
2015-08-13 12:21:55 -04:00
e60c0605e5 Use Visuals drive widget in device init
- Replace custom `drivelist` logic in "device init" with the new `drive`
widget.
2015-08-13 11:56:16 -04:00
085781fa18 Upgrade Resin CLI Visuals to v1.1.0
This version contains the `drive` widget.
2015-08-13 11:55:31 -04:00
08ad8bd5d7 Merge pull request #137 from resin-io/cleanup/slim
Slim down unused functionality
2015-08-13 11:52:57 -04:00
3d36e5f5d3 Remove outdated Windows elevation mechanism
This functionality is outdated and not using anymore due to limitations
in the way it was addressed.

The module and dependencies are removed for now, and will be added back
in the future, once a better approach is planned.
2015-08-13 11:45:50 -04:00
57319f26a6 Slim down unused functionality 2015-08-12 08:17:46 -04:00
11033683fd Stop supporting iojs 2015-08-06 12:12:44 -04:00
e5166c6c8e Use Install-Product instead of Update-NodeJsInstallation 2015-08-06 11:46:57 -04:00
5c96663d1e Merge pull request #134 from resin-io/remove/drive-command
Remove drive command
2015-08-05 12:24:25 -04:00
ffb48c8669 Merge pull request #135 from resin-io/remove/examples-commands
Remove examples commands
2015-08-05 07:04:03 -04:00
12145a2393 Merge pull request #132 from resin-io/upgrade_travis
Test node v0.12 and io.js
2015-08-05 07:03:51 -04:00
f379866c1c Merge pull request #67 from resin-io/feature/wizard
Implement Quickstart command
2015-08-04 13:19:37 -04:00
dc030f4cd1 Implement Quickstart command 2015-08-04 20:16:55 +03:00
b726a2d778 Remove examples commands 2015-08-04 10:00:09 -04:00
a715ec9dc1 Remove drive command 2015-08-04 09:57:59 -04:00
d24b871964 Merge pull request #131 from resin-io/remove/selfupdate
Remove selfupdate functionality
2015-08-04 09:53:48 -04:00
bd0c4e6034 Merge pull request #133 from resin-io/upgrade_appveyor
Add io.js testing in Appveyor
2015-08-04 09:27:36 -04:00
95637e5608 Test node v0.12 and io.js 2015-08-04 14:15:30 +03:00
93b394ed76 Add io.js testing in Appveyor 2015-08-04 13:26:13 +03:00
b515e427ff Merge pull request #129 from resin-io/issue_#73
Add email address to the returned information, when using whoami(). Fix #73.
2015-08-03 16:14:47 -04:00
26b1acf5ef Merge pull request #130 from resin-io/remove/man
Remove man pages
2015-08-03 16:14:23 -04:00
f31eb7c2b5 Add email address to the returned information, when using whoami(). Fix #73. 2015-08-03 21:24:22 +03:00
d423a6ea24 Remove selfupdate functionality
We added this because we thought that knowledge of the supported device types, along with the configuration procedures was going to be encoded in the CLI.

With device specs, this is not longer the case.
2015-08-03 12:20:42 -04:00
4211333e4e Remove man pages 2015-08-03 12:08:49 -04:00
f220e380a7 Merge pull request #125 from resin-io/issue_#117
Display correctly the newly-created application id. Fix #117
2015-07-29 21:43:31 +03:00
9564b4e478 Display correctly the newly-created application id. Fix #117 2015-07-29 21:17:21 +03:00
e2125b8ce9 Fix #73 2015-07-29 21:15:29 +03:00
0bb0e6ea4b Merge pull request #124 from resin-io/integrate-new-resin-visuals-functionality
Integrate new resin-cli-visuals functionality
2015-07-29 09:43:24 -04:00
cf512cc01b Integrate new resin-cli-visuals functionality 2015-07-29 16:34:31 +03:00
fe3e68c3af Merge pull request #121 from resin-io/modify-function-description
Replace device name with uuid, found in resin envs examples in enviroment-variables
2015-07-28 08:49:19 -04:00
0bbfbe36c7 Replace device name with uuid, found in resin envs examples in enviroment-variables 2015-07-27 22:49:11 +03:00
d6ae689593 Merge pull request #118 from resin-io/resin-cli-form
Integrate resin-cli-form
2015-07-27 13:30:48 -04:00
5b5d1be52f Integrate resin-cli-form 2015-07-27 19:50:47 +03:00
cb808869dd Merge pull request #110 from resin-io/support_promises
Add promise support for Resin-SDK dependency
2015-07-24 08:02:40 -04:00
64d83dccfb Add promise support for Resin-SDK dependency 2015-07-24 00:24:17 +03:00
122253bb25 Merge pull request #95 from resin-io/fix/device-registered-at
Add registered_at UNIX epoch
2015-07-23 13:00:29 -04:00
1d53db2854 Add registered_at UNIX epoch 2015-07-23 12:47:49 -04:00
30dc5ea1ea Merge pull request #97 from resin-io/upgrade/dependencies
Upgrade dependencies
2015-07-15 09:08:35 -04:00
58c7ff1f1b Upgrade dependencies 2015-07-15 09:01:50 -04:00
57b9d634be Merge pull request #96 from resin-io/upgrade-visuals
Upgrade resin-cli-visuals dependency
2015-07-14 15:12:54 -04:00
6fb25dea88 Upgrade resin-cli-visuals dependencies 2015-07-14 18:18:19 +03:00
10478e389a Merge pull request #94 from resin-io/upgrade_vcs_dependencies
Upgrade Resin VCS dependencies
2015-07-13 07:37:02 -04:00
46fa4ee2a2 Upgrade Resin VCS dependencies 2015-07-11 00:03:20 +03:00
a7cefe44bf Merge pull request #86 from resin-io/feature/remove-selfupdate
Remove selfupdate functionality. Notify in all cases.
2015-07-09 13:45:09 -04:00
3d03585318 Merge pull request #93 from resin-io/feature/last_seen-undefined
Default device.last_seen to 'Not seen'. Closes #84.
2015-07-09 13:44:59 -04:00
076c3428ee Upgrade Resin CLI Visuals to v0.3.2 2015-07-09 13:43:20 -04:00
9d4ac46985 Default device.last_seen to 'Not seen'. Closes #84. 2015-07-09 09:56:39 -04:00
24edb867fa Upgrade Resin CLI Visuals to v0.3.1 2015-07-09 09:46:08 -04:00
e926ac46c9 Remove selfupdate functionality. Notify in all cases. 2015-07-09 08:18:06 -04:00
9ffd657dbb Merge pull request #85 from resin-io/feature/regenerate-docs
Regenerate docs
2015-07-07 18:40:40 -04:00
aa3cb39551 Regenerate docs 2015-07-07 18:01:25 -04:00
0acbdac66f Use NPM 2.12.1 in Appveyor due to a bug in 2.12.0 2015-07-02 14:28:19 -04:00
5619bdbb67 Resin CLI v0.11.1 2015-06-15 10:09:10 -04:00
381e63bfc9 Merge pull request #70 from resin-io/refactor/new-visuals
Upgrade Resin CLI Visuals and use it's new capabilities
2015-06-12 09:47:30 -04:00
e779347ff2 Merge pull request #69 from resin-io/feature/no-chop-key
Don't chop SSH key
2015-06-11 12:48:16 -04:00
8fa906dd48 Upgrade Resin CLI Visuals and use it's new capabilities 2015-06-11 12:46:56 -04:00
7e5ecd634d HOTFIX: isLoggedIn now returns a possible error 2015-06-11 12:40:49 -04:00
29cf4c1e89 Don't chop SSH key 2015-06-11 08:08:45 -04:00
73736abdea Merge pull request #68 from resin-io/feature/config
Implement config command
2015-06-11 07:57:31 -04:00
ef33156de7 Implement config command 2015-06-10 12:34:42 -04:00
118 changed files with 7899 additions and 4230 deletions

View File

@ -9,4 +9,8 @@ trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
trim_trailing_whitespace = false
[package.json]
indent_style = space
indent_size = 2

2
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,2 @@
- **resin-cli version:**
- **Operating system and architecture:**

18
.gitignore vendored
View File

@ -13,9 +13,6 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
@ -27,9 +24,16 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
bin/node
release/build
npm-shrinkwrap.json
package-lock.json
.resinconf
resinrc.yml
.idea
.vscode
.DS_Store
/tmp
build/
build-bin/
resin-cli-*.zip

View File

@ -1,5 +1,5 @@
coffee_script:
config_file: coffeelint.json
java_script:
javascript:
enabled: false

View File

@ -1,4 +1,28 @@
language: node_js
os:
- linux
- osx
node_js:
- "0.11"
- "0.10"
- "6"
before_install:
- npm -g install npm@4
script: npm run ci
notifications:
email: false
deploy:
- provider: script
script: npm run release
skip_cleanup: true
on:
tags: true
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
repo: resin-io/resin-cli
- provider: npm
email: accounts@resin.io
api_key:
secure: phet6Du13hc1bzStbmpwy2ODNL5BFwjAmnpJ5wMcbWfI7fl0OtQ61s2+vW5hJAvm9fiRLOfiGAEiqOOtoupShZ1X8BNkC708d8+V+iZMoFh3+j6wAEz+N1sVq471PywlOuLAscOcqQNp92giCVt+4VPx2WQYh06nLsunvysGmUM=
skip_cleanup: true
on:
tags: true
condition: "$TRAVIS_TAG =~ ^v?[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+"
repo: resin-io/resin-cli

692
CHANGELOG.md Normal file
View File

@ -0,0 +1,692 @@
# Change Log
All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.10.2 - 2017-11-27
* Inline the entire resin-cli-auth module #721 [Tim Perry]
## v6.10.1 - 2017-11-27
* Set up TypeScript compilation, and make a small start on converting the CLI #720 [Tim Perry]
* Don't commit raw JS build output #720 [Tim Perry]
## v6.10.0 - 2017-11-17
* Allow `os configure` to configure for an app, not just a specific device #718 [Tim Perry]
* Print help even for expected errors #718 [Tim Perry]
## v6.9.0 - 2017-11-16
* Allow non-interactice config generate for simple network settings #714 [Tim Perry]
* Fix issue where network settings were not used by `config generate` #714 [Tim Perry]
## v6.8.3 - 2017-11-16
* Remove resin promote command (which has never worked) to wait for larger resinOS provisioning updates #717 [Tim Perry]
## v6.8.2 - 2017-11-14
* Fix 'cannot read property R_OK of undefined' error in Node >=6 <6.3 #713 [Tim Perry]
## v6.8.1 - 2017-11-09
* Avoid AmbiguousApplication errors in device register when an id is used #711 [Tim Perry]
## v6.8.0 - 2017-10-27
* Allow preloading jetson-tx2 images, improve flasher images detection and remove the --dont-detect-flasher-type-images option. #706 [Alexis Svinartchouk]
## v6.7.4 - 2017-10-25
* Add preload to the CLI docs #702 [Tim Perry]
## v6.7.3 - 2017-10-25
* Allow specifying `--commit=latest` for `resin preload` #701 [Alexis Svinartchouk]
## v6.7.2 - 2017-10-24
* Make update-notifier more resilient and ensure it obeys NO_UPDATE_NOTIFIER, by updating it #699 [Tim Perry]
## v6.7.1 - 2017-10-24
* Respect the -dont-check-device-type option, fix error message #697 [Alexis Svinartchouk]
## v6.7.0 - 2017-10-18
* Added a device api key parameter to the `os configure` command. #487 [Pagan Gazzard]
* Added a `--device-api-key` option to the `config generate` command. #487 [Pagan Gazzard]
* Added a `--device-api-key` option to the `device register` command. #487 [Pagan Gazzard]
## v6.6.13 - 2017-10-18
* Fix issue where `os download` would always download prod images #689 [Tim Perry]
## v6.6.12 - 2017-10-16
* Update resin-preload to 4.0.2 to support preloading Edison images #687 [Alexis Svinartchouk]
## v6.6.11 - 2017-10-13
* Document how to `resin deploy` to an app as a collaborator #685 [Tim Perry]
## v6.6.10 - 2017-10-09
* Ensure hostname truly is optional when configuring device images #676 [Tim Perry]
## v6.6.9 - 2017-10-06
* Fix resin preload --splash-image argument handling #678 [Alexis Svinartchouk]
## v6.6.8 - 2017-10-06
* Ensure analytics failures (e.g. from broken tokens) at startup don't break commands #675 [Tim Perry]
## v6.6.7 - 2017-09-22
* Update to resin-sync, which fixes local push on windows #666 [Tim Perry]
* Add windows instructions to fix node-gyp installs #666 [Tim Perry]
## v6.6.6 - 2017-09-11
* Create ISSUE_TEMPLATE.md #655 [Kostas Lekkas]
## v6.6.5 - 2017-08-31
* Fix lodash bugs in device move & quickstart #643 [Tim Perry]
## v6.6.4 - 2017-08-31
* Catch uncommitted build output automatically in Travis #644 [Tim Perry]
## v6.6.3 - 2017-08-31
* Update README to link to the full CLI command documentation #641 [Tim Perry]
## v6.6.2 - 2017-08-31
* Use DOCKER_HOST from env if possible, and no connection options are available #642 [Tim Perry]
## v6.6.1 - 2017-08-28
* Update resin-preload to 3.1.4 #650 [Alexis Svinartchouk]
## v6.6.0 - 2017-08-28
* Add a --dont-check-device-type option for `resin preload` #647 [Alexis Svinartchouk]
## v6.5.3 - 2017-08-24
* Remove resin-preload build filtering workaround. #645 [Alexis Svinartchouk]
## v6.5.2 - 2017-08-22
### Changed
- Progress bar when preload builds its docker image or fetches the build size;
- Preload will do nothing if you try to preload a build that is already preloaded in the image.
## v6.5.1 - 2017-08-21
### Fixed
- Fix lodash upgrade bug that broke `resin help`
## v6.5.0 - 2017-08-18
### Changed
- Set up Travis npm autodeploy
- Upgrade to Lodash v4, drastically reducing install size (due to dedupes)
- Updated npm package description
- Added support for looking up shared apps via [owner]/[appname] strings
### Added
- Use forked global-tunnel-ng that doesn't proxy connections to socket files
- Preload support
## v6.4.0 - 2017-08-11
### Changed
- Support overlay2 in resin local push
- Support Docker versions greater than 1.X in resin sync
- Provide a helpful warning when Docker is not installed (or is unaccessible)
- Added a link to the Node download page in the warning for users with old Node versions
- Remove inconsistent (and now unneccesary) 'Tagging image as' message from local build output
## v6.3.1 - 2017-08-08
- Updated resin-cli-auth to point to the correct resin dashboard ued for authentication (for example, staging)
## v6.3.0 - 2017-08-03
### Fixed
- Fixed compatibility of `resin local push` with docker >= 1.12
## v6.2.0 - 2017-07-27
### Changed
- Support the new resinOS versions where the sample connection file is called `resin-sample.ignore`
## v6.1.1 - 2017-07-18
### Changed
- Hide the intro quickstart message for now (until it gets renovated)
## v6.1.0 - 2017-06-30
### Fixed
- Fix issue where emulated builds broke Docker `ARG` commands
- Fix issue when using resin deploy with non-standard stdin (e.g. git bash on windows)
### Added
- Bump resin-sync@8.0.1
- Permit resin sync to collaborators
- Fix "'cwd' must be a string" error in Node 8
- Do not explicitly disable ControlMaster option for device SSH connections
## v6.0.0 - 2017-06-26
### Added
- Added support for `--squash` parameter for `resin build`
### Fixed
- **Breaking** Remove Buffer polyfill, require Node v6+, and print warnings in older versions
## v5.11.1 - 2017-06-22
### Added
- Include Node version in Sentry logging
### Fixed
- Ensure to send .pem file contents rather than filename to docker daemon
- Add a polyfill to fix `local configure` in older (<6) Node versions
## v5.11.0 - 2017-06-19
### Added
- `package-lock.json` for `npm5` users
- Added ability to run an emulated build silently with `resin build`
- Gzip images when uploading in `resin deploy`
- Show a clear message immediately as the deploy starts, if we're deploying an image.
- Forced update to the newest resin-sdk (^6.4.1)
- Allow OS version selection when doing `resin device init`
- Actually tolerate the `--yes` param to `resin device init`
- Allows passing `--drive` to `resin device init`
- List detected drives with `resin util available-drives`
- Add the `resin os build-config` method to pass the interactive config step once
and reuse the built file for subsequent `resin os configure` calls (added the new `--config` param to it),
and for `resin device init` (same `--config` param)
- Improve the supported device types listing
### Fixed
- Ensure emulated builds use the correct relative path to qemu when called from any location
- Make emulated builds reliable in the presence for WORKDIR comands
## v5.10.2 - 2017-05-31
### Fixed
- Fixed command line arguments for `resin build`
## v5.10.1 - 2017-05-22
### Fixed
- Fixed breaking bug in `resin local ssh`
## v5.10.0 - 2017-05-22
### Added
- Reduce granularity of update checking to one day
- Include extra usage metadata in error logging to help debugging
- Add uploading of build logs when present with resin deploy
- Highlight cache usage in a local build
- Show a progress bar for upload progress
- Add ability to specify build-time variables for local builds
- Added the proxy support for the `resin ssh` command
### Fixed
- Fixed the not enough unicorns bug in resin build
- Removed the install-time warning for the `valid-email` package
## v5.9.1 - 2017-05-01
### Fixed
- Technical release because v5.9.0 was published earlier (erroneously)
## v5.9.0 - 2017-05-01
### Added
- HTTP(S) proxy support
## v5.8.1 - 2017-04-27
### Fixed
- The `ssh` command was broken
## v5.8.0 - 2017-04-26
### Added
- Add cloud builder output to local build
- Add nocache and tag options to resin deploy
- Add ability to build and deploy an image to resin's infrastructure
### Fixed
- Capture and report errors happening during the program initialization, like parsing invalid YAML config
## v5.7.2 - 2017-04-18
### Fixed
- Fixed warning on NPM install due to dependency conflicts
- Improved node v4 support (some operations are fixed, some will run faster)
## v5.7.1 - 2017-04-03
### Fixed
- Add basic support for the new ResinOS version format
## v5.7.0 - 2017-03-28
### Fixed
- The OS init issues:
* failing to get the superuser resin auth (tried to look into `/root/.resin`)
* failing to write to the drive
## v5.6.1 - 2017-03-23
### Added
- Add Sentry error tracking
### Fixed
- The unneeded warning about the default OS version download from the `device init` command.
- Changed the help references from gitter to forums.
## v5.6.0 - 2017-03-23
### Added
- The `--version` option to the `os download` command. Check `resin help os download` for details.
## v5.5.0 - 2017-03-10
### Added
- Require superuser for scan commands, also introduce docker timeout
- Bump resin-sync@7.0.0: use experimental rds which requires superuser permissions
## v5.4.0 - 2017-03-09
### Added
- Implement 'resin local stop'
- Implement 'resin local'
- Implement 'resin local push'
- Implement 'resin local ssh'
- Implement 'resin local scan'
- Implement 'resin local promote'
- Implement 'resin local logs'
- Implement 'resin local flash'
- Implement 'resin local configure'
### Changed
- Remove app create from primary commands
## v5.3.0 - 2017-03-03
### Added
- `resin sync` AUFS device support
### Changed
- Moved to the new version of `resin-sdk` (via `resin-sdk-preconfigured`)
## v5.2.4 - 2017-01-18
### Changed
- Fix documented requirements for resin ssh and resin sync
## v5.2.3 - 2017-01-04
### Changed
- Add missing `js-yaml` dependency.
## v5.2.2 - 2016-11-01
### Changed
- Fix `shutdown` command not being available.
## v5.2.1 - 2016-10-28
### Changed
- Fix `Boolean options can't have parameters` error in every command.
## v5.2.0 - 2016-10-27
### Added
- Add `shutdown` command.
- Add `--force` option to `device reboot` command.
## v5.1.0 - 2016-09-25
### Added
- Add `devices supported` command.
## v5.0.0 - 2016-09-15
### Added
- Automatically parse '.gitignore' for file inclusions/exclusions from resin sync by default. Skip parsing with `--skip-gitignore`.
- Automatically save options to `<sourceDirectory>/.resin-sync.yml` after every run.
- Support user-specified destination directories with `--destination/-d` option.
- Implement `--after` option to perform actions local (e.g. cleanup) after resin sync has finished.
- Implement interactive dialog for destination directory, with `/usr/src/app` being the default choice.
### Changed
- Require `resin sync` `--source/-s` option if a `.resin-sync.yml` file is not found in the current directory.
- Require `uuid` as an argument in `resin sync/ssh` (`appName` has been removed).
- Always display interactive device selection dialog when uuid is not passed as an argument.
- Disable ControlMaster ssh option (as reported in support).
## v4.5.0 - 2016-09-14
### Added
- Attempt to retrieve device type from the image's first partition.
## v4.4.0 - 2016-08-11
### Changed
- Display OS and Supervisor version in `devices` and `device` commands.
## v4.3.0 - 2016-08-11
### Added
- Implement `device public-url enable` command.
- Implement `device public-url disable` command.
- Implement `device public-url status` command.
- Implement `device public-url` command.
- Add global `--help` option.
## v4.2.1 - 2016-07-26
### Changed
- Fix log messages being `undefined`.
## v4.2.0 - 2016-06-22
### Added
- Add `verbose` option to `resin sync`.
## v4.1.0 - 2016-06-22
### Added
- Add `verbose` option to `resin ssh`.
## v4.0.3 - 2016-05-17
### Changed
- Fix `resin ssh` errors when running in `cmd.exe`.
## v4.0.2 - 2016-04-27
### Changed
- Upgrade `resin-sync` to v2.0.2.
## v4.0.1 - 2016-04-26
### Changed
- Fix unhandled exceptions in the `resin ssh` command.
## v4.0.0 - 2016-04-26
### Added
- Implement `resin ssh` command
### Changed
- Remove `resin sync` `exec` option.
- Upgrade `resin-sync` to v2.0.1.
- Upgrade `resin-sdk` to v5.3.0.
## v3.0.2 - 2016-04-08
### Changed
- Fix `os configure` command not working with shorter uuids.
## v3.0.1 - 2016-03-29
### Changed
- Log Mixpanel events based on the matching command signature.
## v3.0.0 - 2016-03-28
### Added
- Implement `config inject` command.
- Document the case where `EACCES` is thrown during login because of an expired token.
- Integrate `resin-plugin-sync` as a build-in command.
### Changed
- Allow `config generate` to generate a `config.json` for an application.
- Force update alert to always be shown.
- Only throw "Invalid 2FA code" if we're sure that's the cause during `login`.
## v2.7.0 - 2016-03-07
### Added
- Implement `config generate` command.
- Implement `device reboot` command.
## v2.6.2 - 2016-02-19
### Removed
- Remove debugging statement in `quickstart`.
## v2.6.1 - 2016-02-12
### Added
- Documented corrupted image MBR error.
- Show parser device status in `device` command.
- Show id a device is online in `devices` command.
- Make use of static images.
### Changed
- Improve analytics.
- Improve `quickstart` messages.
- Fix `device` example.
## v2.6.0 - 2016-01-21
### Added
- Add support for credential-based authentication.
- Redirect users to GitHub and Gitter in case of errors.
- Add Resin.io ASCII art header on `login`.
- Print an informative next-steps message after `login`.
- Print informative verbose help to `resin help`.
- Support for shorter uuids in all commands.
### Changed
- Change license to Apache 2.0.
- Don't make `device init` a primary command.
- Stop instructing users to run `quickstart` as root.
- Make `login` command purely interactive.
- Handle authentication in `quickstart` if user is not logged in.
- Redirect to `signup` from `login` if user doesn't have an account.
- Make sure to remove registered device resource in case of errors in `quickstart`.
- Upgrade Resin SDK to v5.0.1.
- Upgrade Resin Image Manager to v3.2.6.
- Make `devices` output shorter uuids.
## v2.5.0 - 2015-12-11
### Added
- Show device id in `resin devices`.
- Add helpful instructions after `resin quickstart`.
- Add timestamp to `resin logs` lines.
### Changed
- Lazy load command actions dependencies for performance reasons.
## v2.4.0 - 2015-12-01
### Added
- Show device types when selecting applications.
- Automatic token exchange login with the web dashboard.
### Changed
- Simplify download output messages.
## v2.3.0 - 2015-11-20
### Added
- Implement `settings` command.
- Handle Windows elevation automatically.
### Changed
- Show uuids in `devices` command.
- Clarify resin url in `login` and `whoami`.
## v2.2.0 - 2015-11-12
### Added
- Implement `device move` command.
## v2.1.0 - 2015-11-11
### Added
- Implement `config read` command.
- Implement `config write` command.
### Changed
- Clarify the need of computer password during `sudo` in `os initialize`.
## v2.0.1 - 2015-10-26
### Changed
- Fix critical error when elevating permissions.
## v2.0.0 - 2015-10-26
### Added
- Add `drive` option to `os initialize`.
- Add application name length validation.
- Allow passing a custom uuid to `device register`.
- Add `advanced` option in `device init`.
- Implement user/password login form with 2FA support.
### Changed
- Clarify the need for admin privileges on update.
- Fix non working `yes` option in `os initialize`.
- Fix non working `type` option in `app create`.
- Take device type as an option in `os initialize`.
- Improve the way the update notifier is shown.
- Ignore advanced configuration options by default.
- Fix `device info` shadowing other command help pages.
### Removed
- Remove login with token functionality.
- Remove project directory creation logic in `device init`.
- Remove `app associate` command.
## v1.1.0 - 2015-10-13
### Added
- Implement `os download` command.
- Implement `os configure` command.
- Implement `os initialize` command.
- Implement `device register` command.
- Add TROUBLESHOOTING guide.
- Add a dynamic widget for selecting connected drives.
### Changed
- Make sure temporary files are removed on failures in `device init`.
- Prompt to select application if running `device init` with no arguments.
- Only use admin privileges in `os initialize` to avoid the cache directory to belong to `root`.
- Separate help output per relevance.
- Only print primary command help by default.
- Print plugin warnings in red as the rest of the errors.
- Shorten the length of device await message.
- Upgrade Resin SDK to v3.0.0.
- Check that a directory is a `git` repository before attempting to run `git` operations on it.
- Improve plugin scanning mechanism.
- Fix `EPERM` issue in Windows after a successfull `device init`.
- Fix SDCard burning issues in Windows 10.
- Fix plugins not being loaded in Windows 10.
- Fix bug when listing the available drives in Windows when the user name contains spaces.
- Fix an edge case that caused drives to be scanned incorrectly in OS X.
- Fix operating system not being detected correctly when initializing a device.
### Removed
- Remove outdated information from README.

190
LICENSE
View File

@ -1,21 +1,177 @@
The MIT License
Copyright (c) 2014 Resin.io, Inc. https://resin.io
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
1. Definitions.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

132
README.md
View File

@ -1,66 +1,124 @@
# Resin CLI
Resin CLI
=========
> The official resin.io CLI tool.
[![npm version](https://badge.fury.io/js/resin-cli.svg)](http://badge.fury.io/js/resin-cli)
[![dependencies](https://david-dm.org/resin-io/resin-cli.png)](https://david-dm.org/resin-io/resin-cli.png)
[![Build Status](https://travis-ci.org/resin-io/resin-cli.svg?branch=master)](https://travis-ci.org/resin-io/resin-cli)
[![Build status](https://ci.appveyor.com/api/projects/status/45i7d0m0patxj420?svg=true)](https://ci.appveyor.com/project/jviotti/resin-cli)
[![dependencies](https://david-dm.org/resin-io/resin-cli.svg)](https://david-dm.org/resin-io/resin-cli)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/resin-io/chat)
The official Resin CLI tool.
Requisites
----------
## Installing
If you want to install the CLI directly through npm, you'll need the below. If this looks difficult,
we do now have an experimental standalone binary release available, see ['Standalone install'](#standalone-install) below.
- [NodeJS](https://nodejs.org) (>= v4)
- [Git](https://git-scm.com)
- The following executables should be correctly installed in your shell environment:
- `ssh`: Any recent version of the OpenSSH ssh client (required by `resin sync` and `resin ssh`)
- if you need `ssh` to work behind the proxy you also need [`proxytunnel`](http://proxytunnel.sourceforge.net/) installed (available as `proxytunnel` package for Ubuntu, for example)
- `rsync`: >= 2.6.9 (required by `resin sync`)
##### Windows Support
Before installing resin-cli, you'll need a working node-gyp environment. If you don't already have one you'll see native module build errors during installation. To fix this, run `npm install -g --production windows-build-tools` in an administrator console (available as 'Command Prompt (Admin)' when pressing windows+x in Windows 7+).
`resin sync` and `resin ssh` have not been thoroughly tested on the standard Windows cmd.exe shell. We recommend using bash (or a similar) shell, like Bash for Windows 10 or [Git for Windows](https://git-for-windows.github.io/).
If you still want to use `cmd.exe` you will have to use a package manager like MinGW or chocolatey. For MinGW the steps are:
1. Install [MinGW](http://www.mingw.org).
2. Install the `msys-rsync` and `msys-openssh` packages.
3. Add MinGW to the `%PATH%` if this hasn't been done by the installer already. The location where the binaries are places is usually `C:\MinGW\msys\1.0\bin`, but it can vary if you selected a different location in the installer.
4. Copy your SSH keys to `%homedrive%%homepath\.ssh`.
5. If you need `ssh` to work behind the proxy you also need to install [proxytunnel](http://proxytunnel.sourceforge.net/)
Getting Started
---------------
### NPM install
If you've got all the requirements above, you should be able to install the CLI directly from npm. If not,
or if you have any trouble with this, please try the new standalone install steps just below.
This might require elevated privileges in some environments.
```sh
$ npm install -g resin-cli
$ npm install --global --production resin-cli
```
### Running locally
### Standalone install
If you don't have node or a working pre-gyp environment, you can still install the CLI as a standalone
binary. **This is in experimental and may not work perfectly yet in all environments**, but it seems to work
well in initial cross-platform testing, so it may be useful, and we'd love your feedback if you hit any issues.
To install the CLI as a standalone binary:
* Download the latest zip for your OS from https://github.com/resin-io/resin-cli/releases.
* Extract the contents, putting the `resin-cli` folder somewhere appropriate for your system (e.g. `C:/resin-cli`, `/usr/local/lib/resin-cli`, etc).
* Add the `resin-cli` folder to your `PATH`. (
[Windows instructions](https://www.computerhope.com/issues/ch000549.htm),
[Linux instructions](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix),
[OSX instructions](https://stackoverflow.com/questions/22465332/setting-path-environment-variable-in-osx-permanently))
* Running `resin` in a fresh command line should print the resin CLI help.
To update in future, simply download a new release and replace the extracted folder.
Have any problems, or see any unexpected behaviour? Please file an issue!
### Login
```sh
$ ./bin/resin
$ resin login
```
## Tests
_(Typically useful, but not strictly required for all commands)_
You can run the [Mocha](http://mochajs.org/) test suite, you can do:
### Run commands
```sh
$ gulp test
```
Take a look at the full command documentation at [https://docs.resin.io/tools/cli/](https://docs.resin.io/tools/cli/#table-of-contents
), or by running `resin help`.
## Development mode
---
The following command will watch for any changes and will run a linter and the whole test suite:
Plugins
-------
```sh
$ gulp watch
```
The Resin CLI can be extended with plugins to automate laborious tasks and overall provide a better experience when working with Resin.io. Check the [plugin development tutorial](https://github.com/resin-io/resin-plugin-hello) to learn how to build your own!
If you set `DEBUG` environment variable, errors will print with a stack trace:
FAQ
---
```sh
$ DEBUG=true resin ...
```
### Where is my configuration file?
## Documentation
The per-user configuration file lives in `$HOME/.resinrc.yml` or `%UserProfile%\_resinrc.yml`, in Unix based operating systems and Windows respectively.
You can renegerate the documentation with:
The Resin CLI also attempts to read a `resinrc.yml` file in the current directory, which takes precedence over the per-user configuration file.
```sh
$ npm run-script doc
```
### How do I point the Resin CLI to staging?
## Manual pages
The easiest way is to set the `RESINRC_RESIN_URL=resinstaging.io` environment variable.
UNIX manual pages reside in `man/`
Alternatively, you can edit your configuration file and set `resinUrl: resinstaging.io` to persist this setting.
You can regenerate UNIX `roff` manual pages from markdown with:
### How do I make the Resin CLI persist data in another directory?
```sh
$ gulp man
```
The Resin CLI persists your session token, as well as cached images in `$HOME/.resin` or `%UserProfile%\_resin`.
If you add a new `man` page, remember to add the generated filename to the `man` array in `package.json`.
Pointing the Resin CLI to persist data in another location is necessary in certain environments, like a server, where there is no home directory, or a device running resinOS, which erases all data after a restart.
## Caveats
You can accomplish this by setting `RESINRC_DATA_DIRECTORY=/opt/resin` or adding `dataDirectory: /opt/resin` to your configuration file, replacing `/opt/resin` with your desired directory.
- Some interactive widgets don't work on [Cygwin](https://cygwin.com/). If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
Support
-------
If you're having any problems, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the resin.io team will be happy to help.
You can also get in touch with us in the resin.io [forums](https://forums.resin.io/).
License
-------
The project is licensed under the Apache 2.0 license.

59
TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,59 @@
Troubleshooting
===============
This document contains common issues related to the Resin CLI, and how to fix them.
### After burning to an sdcard, my device doesn't boot
- The downloaded image is not complete (download was interrupted).
Please clean the cache (`%HOME/.resin/cache` or `C:\Users\<user>\_resin\cache`) and run the command again. In the future, the CLI will check that the image is not complete and clean the cache for you.
### I get a permission error when burning to an sdcard
- The SDCard is locked.
### I get EINVAL errors on Cygwin
The errors look something like this:
```
net.js:156
this._handle.open(options.fd);
^
Error: EINVAL, invalid argument
at new Socket (net.js:156:18)
at process.stdin (node.js:664:19)
at Object.Interface.createInterface (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\node_modules\readline2\index.js:31:43)
at PromptUI.UI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\baseUI.js:23:40)
at new PromptUI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\prompt.js:26:8)
at Object.promptModule [as prompt] (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\inquirer.js:27:14)
```
- Some interactive widgets don't work on `Cygwin`. If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
### I get `Invalid MBR boot signature` when configuring a device
This error, accompanied with something like: `Expected 0xAA55, but saw 0x29FE` usually indicates a corrupted device operating system image in the cache, due to bad a internet connection during the download process.
Try clearing the cache with the following command and trying again:
```sh
$ rm -rf $HOME/.resin/cache
```
Or in Windows:
```sh
> del /s /q %UserProfile%\_resin\cache
```
### I get `EACCES: permission denied` when logging in
The Resin CLI stores the session token in `$HOME/.resin` or `C:\Users\<user>\_resin` in UNIX based operating systems and Windows respectively. This error usually indicates that the user doesn't have permissions over that directory, which can happen if you ran the Resin CLI as `root`, and thus the directory got owned by him.
Try resetting the ownership by running:
```sh
$ sudo chown -R <user> $HOME/.resin
```

View File

@ -4,18 +4,22 @@
init:
- git config --global core.autocrlf input
cache:
- C:\Users\appveyor\.node-gyp
- '%AppData%\npm-cache'
matrix:
fast_finish: true
# what combinations to test
environment:
matrix:
- nodejs_version: 0.10
- nodejs_version: 0.11
- nodejs_version: 0.12
- nodejs_version: 6
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm -g install npm@2
- ps: Install-Product node $env:nodejs_version x64
- npm install -g npm@4
- set PATH=%APPDATA%\npm;%PATH%
- npm install -g gulp
- npm install
build: off
@ -23,5 +27,8 @@ build: off
test_script:
- node --version
- npm --version
- ps: gulp test
- cmd: gulp test
- cmd: npm test
deploy_script:
- IF "%APPVEYOR_REPO_TAG%" == "true" (npm run release)
- IF NOT "%APPVEYOR_REPO_TAG%" == "true" (echo 'Not tagged, skipping deploy')

34
automation/build-bin.ts Executable file
View File

@ -0,0 +1,34 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import * as filehound from 'filehound';
import { exec as execPkg } from 'pkg';
const ROOT = path.join(__dirname, '..');
console.log('Building package...\n');
execPkg([
'--target', 'host',
'--output', 'build-bin/resin',
'package.json'
]).then(() => fs.copy(
path.join(ROOT, 'node_modules', 'opn', 'xdg-open'),
path.join(ROOT, 'build-bin', 'xdg-open')
)).then(() => {
return filehound.create()
.paths(path.join(ROOT, 'node_modules'))
.ext(['node', 'dll'])
.find();
}).then((nativeExtensions) => {
console.log(`\nCopying to build-bin:\n${nativeExtensions.join('\n')}`);
return nativeExtensions.map((extPath) => {
return fs.copy(
extPath,
extPath.replace(
path.join(ROOT, 'node_modules'),
path.join(ROOT, 'build-bin')
)
);
});
});

35
automation/custom-types.d.ts vendored Normal file
View File

@ -0,0 +1,35 @@
declare module 'pkg' {
export function exec(args: string[]): Promise<void>;
}
declare module 'filehound' {
export function create(): FileHound;
export interface FileHound {
paths(paths: string[]): FileHound;
paths(...paths: string[]): FileHound;
ext(extensions: string[]): FileHound;
ext(...extensions: string[]): FileHound;
find(): Promise<string[]>;
}
}
declare module 'publish-release' {
interface PublishOptions {
token: string,
owner: string,
repo: string,
tag: string,
name: string,
reuseRelease?: boolean
assets: string[]
}
interface Release {
html_url: string;
}
let publishRelease: (args: PublishOptions, callback: (e: Error, release: Release) => void) => void;
export = publishRelease;
}

53
automation/deploy-bin.ts Normal file
View File

@ -0,0 +1,53 @@
import * as Promise from 'bluebird';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs-extra';
import * as publishRelease from 'publish-release';
import * as archiver from 'archiver';
const publishReleaseAsync = Promise.promisify(publishRelease);
const { GITHUB_TOKEN } = process.env;
const ROOT = path.join(__dirname, '..');
const version = 'v' + require('../package.json').version;
const outputFile = path.join(ROOT, `resin-cli-${version}-${os.platform()}-${os.arch()}.zip`);
new Promise((resolve, reject) => {
console.log('Zipping build...');
let archive = archiver('zip', {
zlib: { level: 7 }
});
archive.directory(path.join(ROOT, 'build-bin'), 'resin-cli');
let outputStream = fs.createWriteStream(outputFile);
outputStream.on('close', resolve);
outputStream.on('error', reject);
archive.on('error', reject);
archive.on('warning', console.warn);
archive.pipe(outputStream);
archive.finalize();
}).then(() => {
console.log('Build zipped');
console.log('Publishing build...');
return publishReleaseAsync({
token: GITHUB_TOKEN,
owner: 'resin-io',
repo: 'resin-cli',
tag: version,
name: `Resin-CLI ${version}`,
reuseRelease: true,
assets: [outputFile]
});
}).then((release) => {
console.log(`Release ${version} successful: ${release.html_url}`);
}).catch((err) => {
console.error('Release failed');
console.error(err);
process.exit(1);
});

15
automation/tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": true,
"sourceMap": true
},
"include": [
"./**/*.ts"
]
}

View File

@ -1,16 +0,0 @@
#!/usr/bin/env node
var os = require('../build/actions/os');
var errors = require('../build/errors');
// TODO: Do some error handling when image or device are incorrect
os.install.action({
image: process.argv[2],
device: process.argv[3]
}, {
yes: true,
fromScript: true
}, function(error) {
return errors.handle(error);
});

View File

@ -1,184 +0,0 @@
(function() {
var _, async, commandOptions, path, resin, vcs, visuals;
path = require('path');
_ = require('lodash-contrib');
async = require('async');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
commandOptions = require('./command-options');
vcs = require('resin-vcs');
exports.create = {
signature: 'app create <name>',
description: 'create an application',
help: 'Use this command to create a new resin.io application.\n\nYou can specify the application type with the `--type` option.\nOtherwise, an interactive dropdown will be shown for you to select from.\n\nYou can see a list of supported device types with\n\n $ resin devices supported\n\nExamples:\n\n $ resin app create MyApp\n $ resin app create MyApp --type raspberry-pi',
options: [
{
signature: 'type',
parameter: 'type',
description: 'application type',
alias: 't'
}
],
permission: 'user',
action: function(params, options, done) {
return async.waterfall([
function(callback) {
return resin.models.application.has(params.name, callback);
}, function(hasApplication, callback) {
if (hasApplication) {
return callback(new Error('You already have an application with that name!'));
}
if (options.type != null) {
return callback(null, options.type);
}
return resin.models.device.getSupportedDeviceTypes(function(error, deviceTypes) {
if (error != null) {
return callback(error);
}
return visuals.widgets.select('Select a type', deviceTypes, callback);
});
}, function(type, callback) {
options.type = type;
return resin.models.application.create(params.name, options.type, callback);
}, function(applicationId, callback) {
console.info("Application created: " + params.name + " (" + options.type + ", id " + applicationId + ")");
return callback();
}
], done);
}
};
exports.list = {
signature: 'apps',
description: 'list all applications',
help: 'Use this command to list all your applications.\n\nNotice this command only shows the most important bits of information for each app.\nIf you want detailed information, use resin app <name> instead.\n\nExamples:\n\n $ resin apps',
permission: 'user',
action: function(params, options, done) {
return resin.models.application.getAll(function(error, applications) {
if (error != null) {
return done(error);
}
console.log(visuals.widgets.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
return done();
});
}
};
exports.info = {
signature: 'app <name>',
description: 'list a single application',
help: 'Use this command to show detailed information for a single application.\n\nExamples:\n\n $ resin app MyApp',
permission: 'user',
action: function(params, options, done) {
return resin.models.application.get(params.name, function(error, application) {
if (error != null) {
return done(error);
}
console.log(visuals.widgets.table.vertical(application, ['id', 'app_name', 'device_type', 'git_repository', 'commit']));
return done();
});
}
};
exports.restart = {
signature: 'app restart <name>',
description: 'restart an application',
help: 'Use this command to restart all devices that belongs to a certain application.\n\nExamples:\n\n $ resin app restart MyApp',
permission: 'user',
action: function(params, options, done) {
return resin.models.application.restart(params.name, done);
}
};
exports.remove = {
signature: 'app rm <name>',
description: 'remove an application',
help: 'Use this command to remove a resin.io application.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin app rm MyApp\n $ resin app rm MyApp --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('application', options.yes, function(callback) {
return resin.models.application.remove(params.name, callback);
}, done);
}
};
exports.associate = {
signature: 'app associate <name>',
description: 'associate a resin project',
help: 'Use this command to associate a project directory with a resin application.\n\nThis command adds a \'resin\' git remote to the directory and runs git init if necessary.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin app associate MyApp\n $ resin app associate MyApp --project my/app/directory',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
var currentDirectory;
currentDirectory = process.cwd();
return async.waterfall([
function(callback) {
return resin.models.application.has(params.name, callback);
}, function(hasApp, callback) {
var message;
if (!hasApp) {
return callback(new Error("Invalid application: " + params.name));
}
message = "Are you sure you want to associate " + currentDirectory + " with " + params.name + "?";
return visuals.patterns.confirm(options.yes, message, callback);
}, function(confirmed, callback) {
if (!confirmed) {
return done();
}
return vcs.initialize(currentDirectory, callback);
}, function(callback) {
return resin.models.application.get(params.name, callback);
}, function(application, callback) {
return vcs.addRemote(currentDirectory, application.git_repository, callback);
}
], function(error, remoteUrl) {
if (error != null) {
return done(error);
}
console.info("git repository added: " + remoteUrl);
return done(null, remoteUrl);
});
}
};
exports.init = {
signature: 'init',
description: 'init an application',
help: 'Use this command to initialise a directory as a resin application.\n\nThis command performs the following steps:\n - Create a resin.io application.\n - Initialize the current directory as a git repository.\n - Add the corresponding git remote to the application.\n\nExamples:\n\n $ resin init\n $ resin init --project my/app/directory',
permission: 'user',
action: function(params, options, done) {
var currentDirectory;
currentDirectory = process.cwd();
return async.waterfall([
function(callback) {
var currentDirectoryBasename;
currentDirectoryBasename = path.basename(currentDirectory);
return visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback);
}, function(applicationName, callback) {
return exports.create.action({
name: applicationName
}, options, function(error) {
if (error != null) {
return callback(error);
}
return callback(null, applicationName);
});
}, function(applicationName, callback) {
return exports.associate.action({
name: applicationName
}, options, callback);
}
], done);
}
};
}).call(this);

View File

@ -1,131 +0,0 @@
(function() {
var TOKEN_URL, _, async, open, resin, settings, url, visuals;
open = require('open');
_ = require('lodash-contrib');
url = require('url');
async = require('async');
resin = require('resin-sdk');
settings = require('resin-settings-client');
visuals = require('resin-cli-visuals');
TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences');
exports.login = {
signature: 'login [token]',
description: 'login to resin.io',
help: "Use this command to login to your resin.io account.\n\nTo login, you need your token, which is accesible from the preferences page:\n\n " + TOKEN_URL + "\n\nExamples:\n\n $ resin login\n $ resin login \"eyJ0eXAiOiJKV1Qi...\"",
action: function(params, options, done) {
return async.waterfall([
function(callback) {
if (params.token != null) {
return callback(null, params.token);
}
console.info("To login to the Resin CLI, you need your unique token, which is accesible from\nthe preferences page at " + TOKEN_URL + "\n\nAttempting to open a browser at that location...");
return open(TOKEN_URL, function(error) {
if (error != null) {
console.error("Unable to open a web browser in the current environment.\nPlease visit " + TOKEN_URL + " manually.");
}
return visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback);
});
}, function(token, callback) {
return resin.auth.loginWithToken(token, callback);
}, function(callback) {
return resin.auth.whoami(callback);
}, function(username, callback) {
console.info("Successfully logged in as: " + username);
return callback();
}
], done);
}
};
exports.logout = {
signature: 'logout',
description: 'logout from resin.io',
help: 'Use this command to logout from your resin.io account.o\n\nExamples:\n\n $ resin logout',
permission: 'user',
action: function(params, options, done) {
return resin.auth.logout(done);
}
};
exports.signup = {
signature: 'signup',
description: 'signup to resin.io',
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin signup --email me@mycompany.com --username johndoe --password ***********\n\n $ resin whoami\n johndoe',
options: [
{
signature: 'email',
parameter: 'email',
description: 'user email',
alias: 'e'
}, {
signature: 'username',
parameter: 'username',
description: 'user name',
alias: 'u'
}, {
signature: 'password',
parameter: 'user password',
description: 'user password',
alias: 'p'
}
],
action: function(params, options, done) {
var hasOptionCredentials;
hasOptionCredentials = !_.isEmpty(options);
if (hasOptionCredentials) {
if (options.email == null) {
return done(new Error('Missing email'));
}
if (options.username == null) {
return done(new Error('Missing username'));
}
if (options.password == null) {
return done(new Error('Missing password'));
}
}
return async.waterfall([
function(callback) {
if (hasOptionCredentials) {
return callback(null, options);
}
return visuals.widgets.register(callback);
}, function(credentials, callback) {
return resin.auth.register(credentials, function(error, token) {
return callback(error, credentials);
});
}, function(credentials, callback) {
return resin.auth.login(credentials, callback);
}
], done);
}
};
exports.whoami = {
signature: 'whoami',
description: 'get current username',
help: 'Use this command to find out the current logged in username.\n\nExamples:\n\n $ resin whoami',
permission: 'user',
action: function(params, options, done) {
return resin.auth.whoami(function(error, username) {
if (error != null) {
return done(error);
}
if (username == null) {
return done(new Error('Username not found'));
}
console.log(username);
return done();
});
}
};
}).call(this);

View File

@ -1,59 +0,0 @@
(function() {
var _;
_ = require('lodash');
exports.yes = {
signature: 'yes',
description: 'confirm non interactively',
boolean: true,
alias: 'y'
};
exports.optionalApplication = {
signature: 'application',
parameter: 'application',
description: 'application name',
alias: ['a', 'app']
};
exports.application = _.defaults({
required: 'You have to specify an application'
}, exports.optionalApplication);
exports.optionalDevice = {
signature: 'device',
parameter: 'device',
description: 'device name',
alias: 'd'
};
exports.booleanDevice = {
signature: 'device',
description: 'device name',
boolean: true,
alias: 'd'
};
exports.network = {
signature: 'network',
parameter: 'network',
description: 'network type',
alias: 'n'
};
exports.wifiSsid = {
signature: 'ssid',
parameter: 'ssid',
description: 'wifi ssid, if network is wifi',
alias: 's'
};
exports.wifiKey = {
signature: 'key',
parameter: 'key',
description: 'wifi key, if network is wifi',
alias: 'k'
};
}).call(this);

View File

@ -1,289 +0,0 @@
(function() {
var _, async, capitano, commandOptions, fse, image, inject, manager, path, pine, registerDevice, resin, tmp, vcs, visuals;
fse = require('fs-extra');
capitano = require('capitano');
_ = require('lodash-contrib');
path = require('path');
async = require('async');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
vcs = require('resin-vcs');
manager = require('resin-image-manager');
image = require('resin-image');
inject = require('resin-config-inject');
registerDevice = require('resin-register-device');
pine = require('resin-pine');
tmp = require('tmp');
tmp.setGracefulCleanup();
commandOptions = require('./command-options');
exports.list = {
signature: 'devices',
description: 'list all devices',
help: 'Use this command to list all devices that belong to you.\n\nYou can filter the devices by application by using the `--application` option.\n\nExamples:\n\n $ resin devices\n $ resin devices --application MyApp\n $ resin devices --app MyApp\n $ resin devices -a MyApp',
options: [commandOptions.optionalApplication],
permission: 'user',
action: function(params, options, done) {
var getFunction;
if (options.application != null) {
getFunction = _.partial(resin.models.device.getAllByApplication, options.application);
} else {
getFunction = resin.models.device.getAll;
}
return getFunction(function(error, devices) {
if (error != null) {
return done(error);
}
console.log(visuals.widgets.table.horizontal(devices, ['id', 'name', 'device_type', 'is_online', 'application_name', 'status', 'last_seen']));
return done(null, devices);
});
}
};
exports.info = {
signature: 'device <name>',
description: 'list a single device',
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device MyDevice',
permission: 'user',
action: function(params, options, done) {
return resin.models.device.get(params.name, function(error, device) {
if (error != null) {
return done(error);
}
console.log(visuals.widgets.table.vertical(device, ['id', 'name', 'device_type', 'is_online', 'ip_address', 'application_name', 'status', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
return done();
});
}
};
exports.remove = {
signature: 'device rm <name>',
description: 'remove a device',
help: 'Use this command to remove a device from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device rm MyDevice\n $ resin device rm MyDevice --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('device', options.yes, function(callback) {
return resin.models.device.remove(params.name, callback);
}, done);
}
};
exports.identify = {
signature: 'device identify <uuid>',
description: 'identify a device with a UUID',
help: 'Use this command to identify a device.\n\nIn the Raspberry Pi, the ACT led is blinked several times.\n\nExamples:\n\n $ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828',
permission: 'user',
action: function(params, options, done) {
return resin.models.device.identify(params.uuid, done);
}
};
exports.rename = {
signature: 'device rename <name> [newName]',
description: 'rename a resin device',
help: 'Use this command to rename a device.\n\nIf you omit the name, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device rename MyDevice MyPi\n $ resin device rename MyDevice',
permission: 'user',
action: function(params, options, done) {
return async.waterfall([
function(callback) {
if (!_.isEmpty(params.newName)) {
return callback(null, params.newName);
}
return visuals.widgets.ask('How do you want to name this device?', null, callback);
}, function(newName, callback) {
return resin.models.device.rename(params.name, newName, callback);
}
], done);
}
};
exports.supported = {
signature: 'devices supported',
description: 'list all supported devices',
help: 'Use this command to get the list of all supported devices\n\nExamples:\n\n $ resin devices supported',
permission: 'user',
action: function(params, options, done) {
return resin.models.device.getSupportedDeviceTypes(function(error, devices) {
if (error != null) {
return done(error);
}
_.each(devices, _.unary(console.log));
return done();
});
}
};
exports.await = {
signature: 'device await <name>',
description: 'await for a device to become online',
help: 'Use this command to await for a device to become online.\n\nThe process will exit when the device becomes online.\n\nNotice that there is no time limit for this command, so it might run forever.\n\nYou can configure the poll interval with the --interval option (defaults to 3000ms).\n\nExamples:\n\n $ resin device await MyDevice\n $ resin device await MyDevice --interval 1000',
options: [
{
signature: 'interval',
parameter: 'interval',
description: 'poll interval',
alias: 'i'
}
],
permission: 'user',
action: function(params, options, done) {
var poll;
if (options.interval == null) {
options.interval = 3000;
}
poll = function() {
return resin.models.device.isOnline(params.name, function(error, isOnline) {
if (error != null) {
return done(error);
}
if (isOnline) {
console.info("Device became online: " + params.name);
return done();
} else {
console.info("Polling device network status: " + params.name);
return setTimeout(poll, options.interval);
}
});
};
return poll();
}
};
exports.init = {
signature: 'device init [device]',
description: 'initialise a device with resin os',
help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNote that this command requires admin privileges.\n\nIf `device` is omitted, you will be prompted to select a device interactively.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nYou can quiet the progress bar and other logging information by passing the `--quiet` boolean option.\n\nYou need to configure the network type and other settings:\n\nEthernet:\n You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".\n\nWifi:\n You can setup the device OS to use wifi by setting the `--network` option to "wifi".\n If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.\n\nYou can omit network related options to be asked about them interactively.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp\n $ resin device init --application MyApp --network ethernet\n $ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret',
options: [commandOptions.optionalApplication, commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey],
permission: 'user',
root: true,
action: function(params, options, done) {
var networkOptions;
networkOptions = {
network: options.network,
wifiSsid: options.ssid,
wifiKey: options.key
};
return async.waterfall([
function(callback) {
if (options.application != null) {
return callback(null, options.application);
}
return vcs.getApplicationName(process.cwd(), callback);
}, function(applicationName, callback) {
options.application = applicationName;
return resin.models.application.has(options.application, callback);
}, function(hasApplication, callback) {
if (!hasApplication) {
return callback(new Error("Invalid application: " + options.application));
}
if (params.device != null) {
return callback(null, params.device);
}
return visuals.patterns.selectDrive(callback);
}, function(device, callback) {
var message;
params.device = device;
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
return visuals.patterns.confirm(options.yes, message, callback);
}, function(confirmed, callback) {
if (!confirmed) {
return done();
}
if (networkOptions.network != null) {
return callback();
}
return visuals.patterns.selectNetworkParameters(function(error, parameters) {
if (error != null) {
return callback(error);
}
_.extend(networkOptions, parameters);
return callback();
});
}, function(callback) {
console.info("Checking application: " + options.application);
return resin.models.application.get(options.application, callback);
}, function(application, callback) {
return async.parallel({
manifest: function(callback) {
console.info('Getting device manifest for the application');
return resin.models.device.getManifestBySlug(application.device_type, callback);
},
config: function(callback) {
console.info('Fetching application configuration');
return resin.models.application.getConfiguration(options.application, networkOptions, callback);
}
}, callback);
}, function(results, callback) {
console.info('Associating the device');
return registerDevice.register(pine, results.config, function(error, device) {
if (error != null) {
return callback(error);
}
results.config.deviceId = device.id;
results.config.uuid = device.uuid;
params.uuid = results.config.uuid;
return callback(null, results);
});
}, function(results, callback) {
var bar, spinner;
console.info('Configuring device operating system image');
if (process.env.DEBUG) {
console.log(results.config);
}
bar = new visuals.widgets.Progress('Downloading Device OS');
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)');
return manager.configure(results.manifest, results.config, function(error, imagePath, removeCallback) {
spinner.stop();
return callback(error, imagePath, removeCallback);
}, function(state) {
if (state != null) {
return bar.update(state);
} else {
return spinner.start();
}
});
}, function(configuredImagePath, removeCallback, callback) {
var bar;
console.info('Attempting to write operating system image to drive');
bar = new visuals.widgets.Progress('Writing Device OS');
return image.write({
device: params.device,
image: configuredImagePath,
progress: _.bind(bar.update, bar)
}, function(error) {
if (error != null) {
return callback(error);
}
return callback(null, configuredImagePath, removeCallback);
});
}, function(temporalImagePath, removeCallback, callback) {
console.info('Image written successfully');
return removeCallback(callback);
}, function(callback) {
return resin.models.device.getByUUID(params.uuid, callback);
}, function(device, callback) {
console.info("Device created: " + device.name);
return callback(null, device.name);
}
], done);
}
};
}).call(this);

View File

@ -1,33 +0,0 @@
(function() {
var _, async, drivelist, visuals;
_ = require('lodash');
async = require('async');
visuals = require('resin-cli-visuals');
drivelist = require('drivelist');
exports.list = {
signature: 'drives',
description: 'list available drives',
help: 'Use this command to list all drives that are connected to your machine.\n\nExamples:\n\n $ resin drives',
permission: 'user',
action: function(params, options, done) {
return drivelist.list(function(error, drives) {
if (error != null) {
return done(error);
}
return async.reject(drives, drivelist.isSystem, function(removableDrives) {
if (_.isEmpty(removableDrives)) {
return done(new Error('No removable devices available'));
}
console.log(visuals.widgets.table.horizontal(removableDrives, ['device', 'description', 'size']));
return done();
});
});
}
};
}).call(this);

View File

@ -1,107 +0,0 @@
(function() {
var _, async, commandOptions, resin, visuals;
async = require('async');
_ = require('lodash-contrib');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
commandOptions = require('./command-options');
exports.list = {
signature: 'envs',
description: 'list all environment variables',
help: 'Use this command to list all environment variables for\na particular application or device.\n\nThis command lists all custom environment variables.\nIf you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application MyApp\n $ resin envs --application MyApp --verbose\n $ resin envs --device MyDevice',
options: [
commandOptions.optionalApplication, commandOptions.optionalDevice, {
signature: 'verbose',
description: 'show private environment variables',
boolean: true,
alias: 'v'
}
],
permission: 'user',
action: function(params, options, done) {
return async.waterfall([
function(callback) {
if (options.application != null) {
return resin.models.environmentVariables.getAllByApplication(options.application, callback);
} else if (options.device != null) {
return resin.models.environmentVariables.device.getAll(options.device, callback);
} else {
return callback(new Error('You must specify an application or device'));
}
}, function(environmentVariables, callback) {
var isSystemVariable;
if (!options.verbose) {
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
environmentVariables = _.reject(environmentVariables, isSystemVariable);
}
console.log(visuals.widgets.table.horizontal(environmentVariables, ['id', 'name', 'value']));
return callback();
}
], done);
}
};
exports.remove = {
signature: 'env rm <id>',
description: 'remove an environment variable',
help: 'Use this command to remove an environment variable from an application.\n\nDon\'t remove resin specific variables, as things might not work as expected.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nIf you want to eliminate a device environment variable, pass the `--device` boolean option.\n\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes\n $ resin env rm 215 --device',
options: [commandOptions.yes, commandOptions.booleanDevice],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('environment variable', options.yes, function(callback) {
if (options.device) {
return resin.models.environmentVariables.device.remove(params.id, callback);
} else {
return resin.models.environmentVariables.remove(params.id, callback);
}
}, done);
}
};
exports.add = {
signature: 'env add <key> [value]',
description: 'add an environment variable',
help: 'Use this command to add an enviroment variable to an application.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\n\nUse the `--device` option if you want to assign the environment variable\nto a specific device.\n\nIf the value is grabbed from the environment, a warning message will be printed.\nUse `--quiet` to remove it.\n\nExamples:\n\n $ resin env add EDITOR vim --application MyApp\n $ resin env add TERM --application MyApp\n $ resin env add EDITOR vim --device MyDevice',
options: [commandOptions.optionalApplication, commandOptions.optionalDevice],
permission: 'user',
action: function(params, options, done) {
if (params.value == null) {
params.value = process.env[params.key];
if (params.value == null) {
return done(new Error("Environment value not found for key: " + params.key));
} else {
console.info("Warning: using " + params.key + "=" + params.value + " from host environment");
}
}
if (options.application != null) {
return resin.models.environmentVariables.create(options.application, params.key, params.value, done);
} else if (options.device != null) {
return resin.models.environmentVariables.device.create(options.device, params.key, params.value, done);
} else {
return done(new Error('You must specify an application or device'));
}
}
};
exports.rename = {
signature: 'env rename <id> <value>',
description: 'rename an environment variable',
help: 'Use this command to rename an enviroment variable from an application.\n\nPass the `--device` boolean option if you want to rename a device environment variable.\n\nExamples:\n\n $ resin env rename 376 emacs\n $ resin env rename 376 emacs --device',
permission: 'user',
options: [commandOptions.booleanDevice],
action: function(params, options, done) {
if (options.device) {
return resin.models.environmentVariables.device.update(params.id, params.value, done);
} else {
return resin.models.environmentVariables.update(params.id, params.value, done);
}
}
};
}).call(this);

View File

@ -1,89 +0,0 @@
(function() {
var _, async, examplesData, fs, mkdirp, path, resin, vcs, visuals;
mkdirp = require('mkdirp');
async = require('async');
fs = require('fs');
path = require('path');
_ = require('lodash');
resin = require('resin-sdk');
visuals = require('resin-cli-visuals');
vcs = require('resin-vcs');
examplesData = require('../data/examples.json');
exports.list = {
signature: 'examples',
description: 'list all example applications',
help: 'Use this command to list available example applications from resin.io\n\nExample:\n\n $ resin examples',
permission: 'user',
action: function() {
examplesData = _.map(examplesData, function(example, index) {
example.id = index + 1;
return example;
});
examplesData = _.map(examplesData, function(example) {
if (example.author == null) {
example.author = 'Unknown';
}
return example;
});
return console.log(visuals.widgets.table.horizontal(examplesData, ['id', 'name', 'display_name', 'repository', 'author']));
}
};
exports.info = {
signature: 'example <name>',
description: 'list a single example application',
help: 'Use this command to show information of a single example application\n\nExample:\n\n $ resin example cimon',
permission: 'user',
action: function(params, options, done) {
var example;
example = _.findWhere(examplesData, {
name: params.name
});
if (example == null) {
return done(new Error("Unknown example: " + params.name));
}
if (example.author == null) {
example.author = 'Unknown';
}
console.log(visuals.widgets.table.vertical(example, ['name', 'display_name', 'description', 'author', 'repository']));
return done();
}
};
exports.clone = {
signature: 'example clone <name>',
description: 'clone an example application',
help: 'Use this command to clone an example application to the current directory\n\nThis command outputs information about the cloning process.\nUse `--quiet` to remove that output.\n\nExample:\n\n $ resin example clone cimon',
permission: 'user',
action: function(params, options, done) {
var currentDirectory, destination, example;
example = _.findWhere(examplesData, {
name: params.name
});
if (example == null) {
return done(new Error("Unknown example: " + params.name));
}
currentDirectory = process.cwd();
destination = path.join(currentDirectory, example.name);
return mkdirp(destination, function(error) {
if (error != null) {
return done(error);
}
console.info("Cloning " + example.display_name + " to " + destination);
vcs.clone(example.repository, destination, done);
return done();
});
}
};
}).call(this);

View File

@ -1,162 +0,0 @@
(function() {
var PADDING_INITIAL, PADDING_MIDDLE, _, addAlias, addOptionPrefix, buildHelpString, buildOptionSignatureHelp, capitano, command, general, getCommandHelp, getFieldMaxLength, getOptionHelp, getOptionsParsedSignatures, resin;
_ = require('lodash');
_.str = require('underscore.string');
resin = require('resin-sdk');
capitano = require('capitano');
PADDING_INITIAL = ' ';
PADDING_MIDDLE = '\t';
getFieldMaxLength = function(array, field) {
return _.max(_.map(array, function(item) {
return item[field].toString().length;
}));
};
buildHelpString = function(firstColumn, secondColumn) {
var result;
result = "" + PADDING_INITIAL + firstColumn;
result += "" + PADDING_MIDDLE + secondColumn;
return result;
};
addOptionPrefix = function(option) {
if (option.length <= 0) {
return;
}
if (option.length === 1) {
return "-" + option;
} else {
return "--" + option;
}
};
addAlias = function(alias) {
return ", " + (addOptionPrefix(alias));
};
buildOptionSignatureHelp = function(option) {
var alias, i, len, ref, result;
result = addOptionPrefix(option.signature.toString());
if (_.isString(option.alias)) {
result += addAlias(option.alias);
} else if (_.isArray(option.alias)) {
ref = option.alias;
for (i = 0, len = ref.length; i < len; i++) {
alias = ref[i];
result += addAlias(alias);
}
}
if (option.parameter != null) {
result += " <" + option.parameter + ">";
}
return result;
};
getCommandHelp = function(command) {
var commandSignature, maxSignatureLength;
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature');
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ');
return buildHelpString(commandSignature, command.description);
};
getOptionsParsedSignatures = function(optionsHelp) {
var maxLength;
maxLength = _.max(_.map(optionsHelp, function(signature) {
return signature.length;
}));
return _.map(optionsHelp, function(signature) {
return _.str.rpad(signature, maxLength, ' ');
});
};
getOptionHelp = function(option, maxLength) {
var result;
result = PADDING_INITIAL;
result += _.str.rpad(option.signature, maxLength, ' ');
result += PADDING_MIDDLE;
result += option.description;
return result;
};
general = function() {
var command, i, j, len, len1, option, optionSignatureMaxLength, options, ref;
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
console.log('Commands:\n');
ref = capitano.state.commands;
for (i = 0, len = ref.length; i < len; i++) {
command = ref[i];
if (command.isWildcard()) {
continue;
}
console.log(getCommandHelp(command));
}
console.log('\nGlobal Options:\n');
options = _.map(capitano.state.globalOptions, function(option) {
option.signature = buildOptionSignatureHelp(option);
return option;
});
optionSignatureMaxLength = _.max(_.map(options, function(option) {
return option.signature.length;
}));
for (j = 0, len1 = options.length; j < len1; j++) {
option = options[j];
console.log(getOptionHelp(option, optionSignatureMaxLength));
}
return console.log();
};
command = function(params, options, done) {
return capitano.state.getMatchCommand(params.command, function(error, command) {
var i, len, option, optionSignatureMaxLength;
if (error != null) {
return done(error);
}
if ((command == null) || command.isWildcard()) {
return capitano.defaults.actions.commandNotFound(params.command);
}
console.log("Usage: " + command.signature);
if (command.help != null) {
console.log("\n" + command.help);
} else if (command.description != null) {
console.log("\n" + (_.str.humanize(command.description)));
}
if (!_.isEmpty(command.options)) {
console.log('\nOptions:\n');
options = _.map(command.options, function(option) {
option.signature = buildOptionSignatureHelp(option);
return option;
});
optionSignatureMaxLength = _.max(_.map(options, function(option) {
return option.signature.toString().length;
}));
for (i = 0, len = options.length; i < len; i++) {
option = options[i];
console.log(getOptionHelp(option, optionSignatureMaxLength));
}
console.log();
}
return done();
});
};
exports.help = {
signature: 'help [command...]',
description: 'show help',
help: 'Get detailed help for an specific command.\n\nExamples:\n\n $ resin help apps\n $ resin help os download',
action: function(params, options, done) {
if (params.command != null) {
return command(params, options, done);
} else {
return general(params, options, done);
}
}
};
}).call(this);

View File

@ -1,19 +0,0 @@
(function() {
module.exports = {
app: require('./app'),
info: require('./info'),
auth: require('./auth'),
drive: require('./drive'),
device: require('./device'),
env: require('./environment-variables'),
keys: require('./keys'),
logs: require('./logs'),
notes: require('./notes'),
preferences: require('./preferences'),
help: require('./help'),
examples: require('./examples'),
plugin: require('./plugin'),
update: require('./update')
};
}).call(this);

View File

@ -1,16 +0,0 @@
(function() {
var packageJSON;
packageJSON = require('../../package.json');
exports.version = {
signature: 'version',
description: 'output the version number',
help: 'Display the Resin CLI version.',
action: function(params, options, done) {
console.log(packageJSON.version);
return done();
}
};
}).call(this);

View File

@ -1,92 +0,0 @@
(function() {
var SSH_KEY_WIDTH, _, async, capitano, commandOptions, fs, resin, visuals;
_ = require('lodash');
_.str = require('underscore.string');
async = require('async');
fs = require('fs');
resin = require('resin-sdk');
capitano = require('capitano');
visuals = require('resin-cli-visuals');
commandOptions = require('./command-options');
exports.list = {
signature: 'keys',
description: 'list all ssh keys',
help: 'Use this command to list all your SSH keys.\n\nExamples:\n\n $ resin keys',
permission: 'user',
action: function(params, options, done) {
return resin.models.key.getAll(function(error, keys) {
if (error != null) {
return done(error);
}
console.log(visuals.widgets.table.horizontal(keys, ['id', 'title']));
return done();
});
}
};
SSH_KEY_WIDTH = 43;
exports.info = {
signature: 'key <id>',
description: 'list a single ssh key',
help: 'Use this command to show information about a single SSH key.\n\nExamples:\n\n $ resin key 17',
permission: 'user',
action: function(params, options, done) {
return resin.models.key.get(params.id, function(error, key) {
if (error != null) {
return done(error);
}
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH);
console.log(visuals.widgets.table.vertical(key, ['id', 'title', 'public_key']));
return done();
});
}
};
exports.remove = {
signature: 'key rm <id>',
description: 'remove a ssh key',
help: 'Use this command to remove a SSH key from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin key rm 17\n $ resin key rm 17 --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('key', options.yes, function(callback) {
return resin.models.key.remove(params.id, callback);
}, done);
}
};
exports.add = {
signature: 'key add <name> [path]',
description: 'add a SSH key to resin.io',
help: 'Use this command to associate a new SSH key with your account.\n\nIf `path` is omitted, the command will attempt\nto read the SSH key from stdin.\n\nExamples:\n\n $ resin key add Main ~/.ssh/id_rsa.pub\n $ cat ~/.ssh/id_rsa.pub | resin key add Main',
permission: 'user',
action: function(params, options, done) {
return async.waterfall([
function(callback) {
if (params.path != null) {
return fs.readFile(params.path, {
encoding: 'utf8'
}, callback);
} else {
return capitano.utils.getStdin(function(data) {
return callback(null, data);
});
}
}, function(key, callback) {
return resin.models.key.create(params.name, key, callback);
}
], done);
}
};
}).call(this);

View File

@ -1,48 +0,0 @@
(function() {
var LOGS_HISTORY_COUNT, _, resin;
_ = require('lodash');
resin = require('resin-sdk');
LOGS_HISTORY_COUNT = 200;
module.exports = {
signature: 'logs <uuid>',
description: 'show device logs',
help: 'Use this command to show logs for a specific device.\n\nBy default, the command prints all log messages and exit.\n\nTo limit the output to the n last lines, use the `--num` option along with a number.\nThis is similar to doing `resin logs <uuid> | tail -n X`.\n\nTo continuously stream output, and see new logs in real time, use the `--tail` option.\n\nNote that for now you need to provide the whole UUID for this command to work correctly.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail',
options: [
{
signature: 'num',
parameter: 'num',
description: 'number of lines to display',
alias: 'n'
}, {
signature: 'tail',
description: 'continuously stream output',
boolean: true,
alias: 't'
}
],
permission: 'user',
action: function(params, options, done) {
return resin.logs.subscribe(params.uuid, {
history: options.num || LOGS_HISTORY_COUNT,
tail: options.tail
}, function(error, message) {
if (error != null) {
return done(error);
}
if (_.isArray(message)) {
_.each(message, function(line) {
return console.log(line.message);
});
} else {
console.log(message.message);
}
return done();
});
}
};
}).call(this);

View File

@ -1,32 +0,0 @@
(function() {
var _, async, resin;
_ = require('lodash');
async = require('async');
resin = require('resin-sdk');
exports.set = {
signature: 'note <|note>',
description: 'set a device note',
help: 'Use this command to set or update a device note.\n\nIf note command isn\'t passed, the tool attempts to read from `stdin`.\n\nTo view the notes, use $ resin device <name>.\n\nExamples:\n\n $ resin note "My useful note" --device MyDevice\n $ cat note.txt | resin note --device MyDevice',
options: [
{
signature: 'device',
parameter: 'device',
description: 'device name',
alias: ['d', 'dev'],
required: 'You have to specify a device'
}
],
permission: 'user',
action: function(params, options, done) {
if (_.isEmpty(params.note)) {
return done(new Error('Missing note content'));
}
return resin.models.device.note(options.device, params.note, done);
}
};
}).call(this);

View File

@ -1,83 +0,0 @@
(function() {
var _, commandOptions, plugins, visuals;
_ = require('lodash');
visuals = require('resin-cli-visuals');
commandOptions = require('./command-options');
plugins = require('../plugins');
exports.list = {
signature: 'plugins',
description: 'list all plugins',
help: 'Use this command to list all the installed resin plugins.\n\nExamples:\n\n $ resin plugins',
permission: 'user',
action: function(params, options, done) {
return plugins.list(function(error, resinPlugins) {
if (error != null) {
return done(error);
}
if (_.isEmpty(resinPlugins)) {
console.log('You don\'t have any plugins yet');
return done();
}
console.log(visuals.widgets.table.horizontal(resinPlugins, ['name', 'version', 'description', 'license']));
return done();
});
}
};
exports.install = {
signature: 'plugin install <name>',
description: 'install a plugin',
help: 'Use this command to install a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin install hello',
permission: 'user',
action: function(params, options, done) {
return plugins.install(params.name, function(error) {
if (error != null) {
return done(error);
}
console.info("Plugin installed: " + params.name);
return done();
});
}
};
exports.update = {
signature: 'plugin update <name>',
description: 'update a plugin',
help: 'Use this command to update a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin update hello',
permission: 'user',
action: function(params, options, done) {
return plugins.update(params.name, function(error, version) {
if (error != null) {
return done(error);
}
console.info("Plugin updated: " + params.name + "@" + version);
return done();
});
}
};
exports.remove = {
signature: 'plugin rm <name>',
description: 'remove a plugin',
help: 'Use this command to remove a resin.io plugin.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin plugin rm hello\n $ resin plugin rm hello --yes',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('plugin', options.yes, function(callback) {
return plugins.remove(params.name, callback);
}, function(error) {
if (error != null) {
return done(error);
}
console.info("Plugin removed: " + params.name);
return done();
});
}
};
}).call(this);

View File

@ -1,22 +0,0 @@
(function() {
var open, settings, url;
open = require('open');
url = require('url');
settings = require('resin-settings-client');
exports.preferences = {
signature: 'preferences',
description: 'open preferences form',
help: 'Use this command to open the preferences form.\n\nIn the future, we will allow changing all preferences directly from the terminal.\nFor now, we open your default web browser and point it to the web based preferences form.\n\nExamples:\n\n $ resin preferences',
permission: 'user',
action: function() {
var absUrl;
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences');
return open(absUrl);
}
};
}).call(this);

View File

@ -1,23 +0,0 @@
(function() {
var packageJSON, selfupdate;
selfupdate = require('selfupdate');
packageJSON = require('../../package.json');
exports.update = {
signature: 'update',
description: 'update the resin cli',
help: 'Use this command to update the Resin CLI\n\nThis command outputs information about the update process.\nUse `--quiet` to remove that output.\n\nThe Resin CLI checks for updates once per day.\n\nMajor updates require a manual update with this update command,\nwhile minor updates are applied automatically.\n\nExamples:\n\n $ resin update',
action: function(params, options, done) {
return selfupdate.update(packageJSON, function(error, version) {
if (error != null) {
return done(error);
}
console.info("Updated " + packageJSON.name + " to version " + version + ".");
return done();
});
}
};
}).call(this);

View File

@ -1,177 +0,0 @@
(function() {
var _, actions, async, capitano, changeProjectDirectory, errors, plugins, resin, update;
_ = require('lodash');
async = require('async');
capitano = require('capitano');
resin = require('resin-sdk');
actions = require('./actions');
errors = require('./errors');
plugins = require('./plugins');
update = require('./update');
capitano.permission('user', function(done) {
return resin.auth.isLoggedIn(function(isLoggedIn) {
if (!isLoggedIn) {
return done(new Error('You have to log in'));
}
return done();
});
});
capitano.command({
signature: '*',
action: function() {
return capitano.execute({
command: 'help'
});
}
});
capitano.globalOption({
signature: 'quiet',
description: 'quiet (no output)',
boolean: true,
alias: 'q'
});
capitano.globalOption({
signature: 'project',
parameter: 'path',
description: 'project path',
alias: 'j'
});
capitano.globalOption({
signature: 'version',
description: actions.info.version.description,
boolean: true,
alias: 'v'
});
capitano.globalOption({
signature: 'no-color',
description: 'disable colour highlighting',
boolean: true
});
capitano.command(actions.info.version);
capitano.command(actions.help.help);
capitano.command(actions.auth.login);
capitano.command(actions.auth.logout);
capitano.command(actions.auth.signup);
capitano.command(actions.auth.whoami);
capitano.command(actions.app.create);
capitano.command(actions.app.list);
capitano.command(actions.app.remove);
capitano.command(actions.app.restart);
capitano.command(actions.app.associate);
capitano.command(actions.app.init);
capitano.command(actions.app.info);
capitano.command(actions.device.list);
capitano.command(actions.device.supported);
capitano.command(actions.device.rename);
capitano.command(actions.device.init);
capitano.command(actions.device.await);
capitano.command(actions.device.info);
capitano.command(actions.device.remove);
capitano.command(actions.device.identify);
capitano.command(actions.drive.list);
capitano.command(actions.notes.set);
capitano.command(actions.preferences.preferences);
capitano.command(actions.keys.list);
capitano.command(actions.keys.add);
capitano.command(actions.keys.info);
capitano.command(actions.keys.remove);
capitano.command(actions.env.list);
capitano.command(actions.env.add);
capitano.command(actions.env.rename);
capitano.command(actions.env.remove);
capitano.command(actions.logs);
capitano.command(actions.examples.list);
capitano.command(actions.examples.clone);
capitano.command(actions.examples.info);
capitano.command(actions.plugin.list);
capitano.command(actions.plugin.install);
capitano.command(actions.plugin.update);
capitano.command(actions.plugin.remove);
capitano.command(actions.update.update);
changeProjectDirectory = function(directory) {
try {
return process.chdir(directory);
} catch (_error) {
return errors.handle(new Error("Invalid project: " + directory));
}
};
async.waterfall([
function(callback) {
return update.check(callback);
}, function(callback) {
return plugins.register('resin-plugin-', callback);
}, function(callback) {
var cli;
cli = capitano.parse(process.argv);
if (cli.global.quiet || !process.stdout.isTTY) {
console.info = _.noop;
}
if (cli.global.project != null) {
changeProjectDirectory(cli.global.project);
}
if (cli.global.version) {
return actions.info.version.action(null, null, callback);
} else {
return capitano.execute(cli, callback);
}
}
], errors.handle);
}).call(this);

View File

@ -1,96 +0,0 @@
[
{
"name": "basic-resin-node-project",
"display_name": "Node.js Starter Project",
"repository": "https://github.com/resin-io/basic-resin-node-project",
"description": "This is a simple Hello, World project for node.js designed to act as a basis for future work. It demonstrates how to install native Linux packages and configure your application."
},
{
"name": "cimon",
"display_name": "Cimon",
"repository": "https://bitbucket.org/efwe/cimon",
"description": "A simple tool for reading temperatures from a USB-enabled thermometer. This project is used as the backend to efwe's awesome temperature visualisation at 123k.de.",
"author": "efwe"
},
{
"name": "firebaseDTL",
"display_name": "Digital Temperature Logger",
"repository": "https://github.com/shaunmulligan/firebaseDTL",
"description": "A Firebase-backed Digital Temperature Logger allowing you to connect devices with multiple temperature sensors to a central cloud-based datastore.",
"author": "Shaun Mulligan"
},
{
"name": "digitiser",
"display_name": "Digitiser",
"repository": "https://github.com/shaunmulligan/digitiser",
"description": "A tool for displaying integer values from a JSON endpoint on a MAX7219 7-segment display.",
"author": "Shaun Mulligan"
},
{
"name": "basic-gpio",
"display_name": "Example Pi Pins Application",
"repository": "https://github.com/shaunmulligan/basic-gpio",
"description": "A simple application which demonstrates the use of the Pi Pins library to interface with GPIO.",
"author": "Shaun Mulligan"
},
{
"name": "coder",
"display_name": "Google Coder",
"repository": "https://github.com/resin-io/coder",
"description": "Resin.io-enabled version of Google's excellent Coder project which makes it easy to develop web projects on your device."
},
{
"name": "hoversnap",
"display_name": "Hoversnap",
"repository": "https://github.com/resin-io/hoversnap",
"description": "A tool for controlling a camera using a foot switch in order to capture shots in which people appear to be flying."
},
{
"name": "resin-piminer",
"display_name": "Pi Miner",
"repository": "https://github.com/csquared/resin-piminer",
"description": "A bitcoin miner for the Raspberry Pi.",
"author": "Chris Continanza"
},
{
"name": "resin-cctv",
"display_name": "Resin CCTV",
"repository": "https://github.com/abresas/resin-cctv",
"description": "A project which allows you to use your devices as a CCTV camera system which hooks into Dropbox.",
"author": "Aleksis Brezas"
},
{
"name": "resin_player",
"display_name": "Resin Player",
"repository": "https://bitbucket.org/lifeeth/resin_player/",
"description": "A project which allows you to play squeezebox media through your devices.",
"author": "Praneeth Bodduluri"
},
{
"name": "salesforceTemp",
"display_name": "Salesforce Temperature Probe",
"repository": "https://github.com/shaunmulligan/salesforceTemp",
"description": "Example application for interfacing with a temperature probe using Salesforce.com.",
"author": "Shaun Mulligan"
},
{
"name": "sms2speech",
"display_name": "SMS to Speech",
"repository": "https://github.com/alexandrosm/sms2speech",
"description": "A simple tool which uses Twillio to read out incoming SMS messages.",
"author": "Alexandros Marinos"
},
{
"name": "resin-kiosk",
"display_name": "Simple Digitiser Kiosk",
"repository": "https://bitbucket.org/lifeeth/resin-kiosk",
"description": "Displays values from a JSON endpoint on your browser in kiosk mode",
"author": "Praneeth Bodduluri"
},
{
"name": "text2speech",
"display_name": "Text to Speech Converter",
"repository": "https://github.com/resin-io/text2speech",
"description": "A simple application that makes your device speak out loud."
}
]

View File

@ -1,25 +0,0 @@
(function() {
var _, isWindows, os, path;
_ = require('lodash');
os = require('os');
path = require('path');
isWindows = function() {
return os.platform() === 'win32';
};
exports.shouldElevate = function(error) {
return _.all([isWindows(), error.code === 'EPERM' || error.code === 'EACCES']);
};
exports.run = function(command) {
if (!isWindows()) {
return;
}
return require('windosu').exec(command);
};
}).call(this);

View File

@ -1,47 +0,0 @@
(function() {
var _, os;
_ = require('lodash');
os = require('os');
exports.handle = function(error, exit) {
var errorCode, message;
if (exit == null) {
exit = true;
}
if ((error == null) || !(error instanceof Error)) {
return;
}
if (process.env.DEBUG) {
console.error(error.stack);
} else {
if (error.code === 'EISDIR') {
console.error("File is a directory: " + error.path);
} else if (error.code === 'ENOENT') {
console.error("No such file or directory: " + error.path);
} else if (error.code === 'EACCES' || error.code === 'EPERM') {
message = 'You don\'t have enough privileges to run this operation.\n';
if (os.platform() === 'win32') {
message += 'Run a new Command Prompt as administrator and try running this command again.';
} else {
message += 'Try running this command again prefixing it with `sudo`.';
}
console.error(message);
} else if (error.code === 'ENOGIT') {
console.error('Git is not installed on this system.\nHead over to http://git-scm.com to install it and run this command again.');
} else if (error.message != null) {
console.error(error.message);
}
}
if (_.isNumber(error.exitCode)) {
errorCode = error.exitCode;
} else {
errorCode = 1;
}
if (exit) {
return process.exit(errorCode);
}
};
}).call(this);

View File

@ -1,55 +0,0 @@
(function() {
var Nplugm, _, capitano, nplugm, registerPlugin;
Nplugm = require('nplugm');
_ = require('lodash');
capitano = require('capitano');
nplugm = null;
registerPlugin = function(plugin) {
if (!_.isArray(plugin)) {
return capitano.command(plugin);
}
return _.each(plugin, capitano.command);
};
exports.register = function(prefix, callback) {
nplugm = new Nplugm(prefix);
return nplugm.list(function(error, plugins) {
var i, len, plugin;
if (error != null) {
return callback(error);
}
for (i = 0, len = plugins.length; i < len; i++) {
plugin = plugins[i];
try {
registerPlugin(nplugm.require(plugin));
} catch (_error) {
error = _error;
console.error(error.message);
}
}
return callback();
});
};
exports.list = function() {
return nplugm.list.apply(nplugm, arguments);
};
exports.install = function() {
return nplugm.install.apply(nplugm, arguments);
};
exports.update = function() {
return nplugm.update.apply(nplugm, arguments);
};
exports.remove = function() {
return nplugm.remove.apply(nplugm, arguments);
};
}).call(this);

View File

@ -1,37 +0,0 @@
(function() {
var packageJSON, updateAction, updateNotifier;
updateNotifier = require('update-notifier');
packageJSON = require('../package.json');
updateAction = require('./actions/update');
exports.perform = function(callback) {
return updateAction.update.action(null, null, callback);
};
exports.notify = function(update) {
if (!process.stdout.isTTY) {
return;
}
return console.log("> Major update available: " + update.current + " -> " + update.latest + "\n> Run resin update to update.\n> Beware that a major release might introduce breaking changes.\n");
};
exports.check = function(callback) {
var notifier;
notifier = updateNotifier({
pkg: packageJSON
});
if (notifier.update == null) {
return callback();
}
if (notifier.update.type === 'major') {
exports.notify(notifier.update);
return callback();
}
console.log("Performing " + notifier.update.type + " update, hold tight...");
return exports.perform(callback);
};
}).call(this);

115
capitanodoc.coffee Normal file
View File

@ -0,0 +1,115 @@
# coffeelint: disable=max_line_length
module.exports =
title: 'Resin CLI Documentation'
introduction: '''
This tool allows you to interact with the resin.io api from the comfort of your command line.
Please make sure your system meets the requirements as specified in the [README](https://github.com/resin-io/resin-cli).
To get started download the CLI from npm.
$ npm install resin-cli -g
Then authenticate yourself:
$ resin login
Now you have access to all the commands referenced below.
## Proxy support
The CLI does support HTTP(S) proxies.
You can configure the proxy using several methods (in order of their precedence):
* set the `RESINRC_PROXY` environment variable in the URL format (with protocol, host, port, and optionally the basic auth),
* use the [resin config file](https://www.npmjs.com/package/resin-settings-client#documentation) (project-specific or user-level)
and set the `proxy` setting. This can be:
* a string in the URL format,
* or an object following [this format](https://www.npmjs.com/package/global-tunnel-ng#options), which allows more control,
* or set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
environment variable (in the same standard URL format).
'''
categories: [
{
title: 'Application'
files: [ 'lib/actions/app.coffee' ]
},
{
title: 'Authentication',
files: [ 'lib/actions/auth.coffee' ]
},
{
title: 'Device',
files: [ 'lib/actions/device.coffee' ]
},
{
title: 'Environment Variables',
files: [ 'lib/actions/environment-variables.coffee' ]
},
{
title: 'Help',
files: [ 'lib/actions/help.coffee' ]
},
{
title: 'Information',
files: [ 'lib/actions/info.coffee' ]
},
{
title: 'Keys',
files: [ 'lib/actions/keys.coffee' ]
},
{
title: 'Logs',
files: [ 'lib/actions/logs.coffee' ]
},
{
title: 'Sync',
files: [ 'lib/actions/sync.coffee' ]
},
{
title: 'SSH',
files: [ 'lib/actions/ssh.coffee' ]
},
{
title: 'Notes',
files: [ 'lib/actions/notes.coffee' ]
},
{
title: 'OS',
files: [ 'lib/actions/os.coffee' ]
},
{
title: 'Config',
files: [ 'lib/actions/config.coffee' ]
},
{
title: 'Preload',
files: [ 'lib/actions/preload.coffee' ]
},
{
title: 'Settings',
files: [ 'lib/actions/settings.coffee' ]
},
{
title: 'Wizard',
files: [ 'lib/actions/wizard.coffee' ]
},
{
title: 'Local',
files: [ 'lib/actions/local/index.coffee' ]
},
{
title: 'Deploy',
files: [
'lib/actions/build.coffee'
'lib/actions/deploy.coffee'
]
},
{
title: 'Utilities',
files: [ 'lib/actions/util.coffee' ]
},
]

View File

@ -1,96 +0,0 @@
{
"title": "Resin CLI Documentation",
"introduction": "This tool allows you to interact with the resin.io api from the comfort of your command line.\n\nTo get started download the CLI from npm.\n\n\t$ npm install resin-cli -g\n\nThen authenticate yourself:\n\n\t$ resin login\n\nNow you have access to all the commands referenced below.",
"categories": [
{
"title": "Application",
"files": [
"lib/actions/app.coffee"
]
},
{
"title": "Authentication",
"files": [
"lib/actions/auth.coffee"
]
},
{
"title": "Device",
"files": [
"lib/actions/device.coffee"
]
},
{
"title": "Drive",
"files": [
"lib/actions/drive.coffee"
]
},
{
"title": "Environment Variables",
"files": [
"lib/actions/environment-variables.coffee"
]
},
{
"title": "Examples",
"files": [
"lib/actions/examples.coffee"
]
},
{
"title": "Help",
"files": [
"lib/actions/help.coffee"
]
},
{
"title": "Information",
"files": [
"lib/actions/info.coffee"
]
},
{
"title": "Keys",
"files": [
"lib/actions/keys.coffee"
]
},
{
"title": "Logs",
"files": [
"lib/actions/logs.coffee"
]
},
{
"title": "Notes",
"files": [
"lib/actions/notes.coffee"
]
},
{
"title": "OS",
"files": [
"lib/actions/os.coffee"
]
},
{
"title": "Plugin",
"files": [
"lib/actions/plugin.coffee"
]
},
{
"title": "Preferences",
"files": [
"lib/actions/preferences.coffee"
]
},
{
"title": "Update",
"files": [
"lib/actions/update.coffee"
]
}
]
}

View File

@ -1,49 +0,0 @@
_resin() {
COMPREPLY=()
local current="${COMP_WORDS[COMP_CWORD]}"
local previous="${COMP_WORDS[COMP_CWORD-1]}"
local options="version help login logout signup drive whoami app apps init devices device note preferences keys key envs env logs os examples example"
case "${previous}" in
app)
local subcommands="create rm restart"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
drive)
local subcommands="list"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
devices)
local subcommands="supported"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
device)
local subcommands="rename rm identify init"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
key)
local subcommands="add rm"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
env)
local subcommands="add rename rm"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
os)
local subcommands="download install"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
example)
local subcommands="clone"
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
return 0 ;;
*)
;;
esac
COMPREPLY=( $(compgen -W "${options}" -- ${current}) )
return 0
}
complete -F _resin resin

112
doc/automated-init.md Normal file
View File

@ -0,0 +1,112 @@
# Provisioning Resin.io devices in automated (non-interactive) mode
This document describes how to run the `device init` command in non-interactive mode.
It requires collecting some preliminary information _once_.
The final command to provision the device looks like this:
```bash
resin device init --app APP_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
```
You can run this command as many times as you need, putting the new medium (SD card / USB stick) each time.
But before you can run it you need to collect the parameters and build the configuration file. Keep reading to figure out how to do it.
## Collect all the required parameters.
1. `DEVICE_TYPE`. Run
```bash
resin devices supported
```
and find the _slug_ for your target device type, like _raspberrypi3_.
1. `APP_ID`. Create an application (`resin app create APP_NAME --type DEVICE_TYPE`) or find an existing one (`resin apps`) and notice its ID.
1. `OS_VERSION`. Run
```bash
resin os versions DEVICE_TYPE
```
and pick the version that you need, like _v2.0.6+rev1.prod_.
_Note_ that even though we support _semver ranges_ it's recommended to use the exact version when doing the automated provisioning as it
guarantees full compatibility between the steps.
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
```bash
resin util available-drives
```
and get the drive name, like _/dev/sdb_ or _/dev/mmcblk0_.
The resin CLI will not display the system drives to protect you,
but still please check very carefully that you've picked the correct drive as it will be erased during the provisioning process.
Now we have all the parameters -- time to build the config file.
## Build the config file
Interactive device provisioning process often includes collecting some extra device configuration, like the networking mode and wifi credentials.
To skip this interactive step we need to buid this configuration once and save it to the JSON file for later reuse.
Let's say we will place it into the `CONFIG_FILE` path, like _./resin-os/raspberrypi3-config.json_.
We also need to put the OS image somewhere, let's call this path `OS_IMAGE_PATH`, it can be something like _./resin-os/raspberrypi3-v2.0.6+rev1.prod.img_.
1. First we need to download the OS image once. That's needed for building the config, and will speedup the subsequent operations as the downloaded OS image is placed into the local cache.
Run:
```bash
resin os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
```
1. Now we're ready to build the config:
```bash
resin os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
```
This will run you through the interactive configuration wizard and in the end save the generated config as `CONFIG_FILE`. You can then verify it's not empty:
```bash
cat CONFIG_FILE
```
## Done
Now you're ready to run the command in the beginning of this guide.
Please note again that all of these steps only need to be done once (unless you need to change something), and once all the parameters are collected the main init command can be run unchanged.
But there are still some nuances to cover, please read below.
## Nuances
### `sudo` password on *nix systems
In order to write the image to the raw device we need the root permissions, this is unavoidable.
To improve the security we only run the minimal subcommand with `sudo`.
This means that with the default setup you're interrupted closer to the end of the device init process to enter your sudo password for this subcommand to work.
There are several ways to eliminate it and make the process fully non-interactive.
#### Option 1: make passwordless sudo.
Obviously you shouldn't do that if the machine you're working on has access to any sensitive resources or information.
But if you're using a machine dedicated to resin provisioning this can be fine, and also the simplest thing to do.
#### Option 2: `NOPASSWD` directive
You can configure the `resin` CLI command to be sudo-runnable without the password. Check [this post](https://askubuntu.com/questions/159007/how-do-i-run-specific-sudo-commands-without-a-password) for an example.
### Extra initialization config
As of June 2017 all the supported devices should not require any other interactive configuration.
But by the design of our system it is _possible_ (though it doesn't look very likely it's going to happen any time soon) that some extra initialization options may be requested for the specific device types.
If that is the case please raise the issue in the resin CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
_ = require('lodash')
capitanodoc = require('../../capitanodoc.json')
path = require('path')
capitanodoc = require('../../capitanodoc')
markdown = require('./markdown')
result = {}
@ -13,10 +14,13 @@ for commandCategory in capitanodoc.categories
category.commands = []
for file in commandCategory.files
actions = require(file)
actions = require(path.join(process.cwd(), file))
for actionName, actionCommand of actions
category.commands.push(_.omit(actionCommand, 'action'))
if actions.signature?
category.commands.push(_.omit(actions, 'action'))
else
for actionName, actionCommand of actions
category.commands.push(_.omit(actionCommand, 'action'))
result.categories.push(category)
@ -25,10 +29,7 @@ result.toc = _.map result.toc, (category) ->
category.commands = _.map category.commands, (command) ->
return {
signature: command.signature
# TODO: Make anchor prefix a configurable setting
# in capitanodoc.json
anchor: '#/pages/using/cli.md#' + command.signature
anchor: '#' + command.signature
.replace(/\s/g,'-')
.replace(/</g, '60-')
.replace(/>/g, '-62-')

View File

@ -1,10 +1,9 @@
mkdirp = require('mkdirp')
path = require('path')
gulp = require('gulp')
mocha = require('gulp-mocha')
coffee = require('gulp-coffee')
markedMan = require('gulp-marked-man')
coffeelint = require('gulp-coffeelint')
inlinesource = require('gulp-inline-source')
mocha = require('gulp-mocha')
shell = require('gulp-shell')
packageJSON = require('./package.json')
@ -13,32 +12,20 @@ OPTIONS =
coffeelint: path.join(__dirname, 'coffeelint.json')
files:
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
app: 'lib/**/*.coffee'
tests: 'tests/**/*.spec.coffee'
json: [ 'lib/**/*.json' ]
man: 'man/**/*.md'
pages: 'lib/auth/pages/*.ejs'
directories:
man: 'man/'
build: 'build/'
gulp.task 'man', ->
gulp.src(OPTIONS.files.man)
.pipe(markedMan())
.pipe(gulp.dest(OPTIONS.directories.man))
gulp.task 'pages', ->
gulp.src(OPTIONS.files.pages)
.pipe(inlinesource())
.pipe(gulp.dest('build/auth/pages'))
gulp.task 'test', ->
gulp.src(OPTIONS.files.tests, read: false)
.pipe(mocha({
reporter: 'min'
}))
gulp.task 'coffee', [ 'test', 'lint', 'json' ], ->
gulp.task 'coffee', [ 'lint' ], ->
gulp.src(OPTIONS.files.app)
.pipe(coffee())
.pipe(gulp.dest(OPTIONS.directories.build))
gulp.task 'json', ->
gulp.src(OPTIONS.files.json)
.pipe(coffee(bare: true, header: true))
.pipe(gulp.dest(OPTIONS.directories.build))
gulp.task 'lint', ->
@ -48,11 +35,16 @@ gulp.task 'lint', ->
}))
.pipe(coffeelint.reporter())
gulp.task 'test', ->
gulp.src(OPTIONS.files.tests, read: false)
.pipe(mocha({
reporter: 'min'
}))
gulp.task 'build', [
'coffee'
'man'
'coffee',
'pages'
]
gulp.task 'watch', [ 'test', 'lint', 'coffee' ], ->
gulp.watch([ OPTIONS.files.coffee, OPTIONS.files.json ], [ 'coffee' ])
gulp.watch([ OPTIONS.files.man ], [ 'man' ])
gulp.task 'watch', [ 'build' ], ->
gulp.watch([ OPTIONS.files.coffee ], [ 'build' ])

View File

@ -1,10 +1,20 @@
path = require('path')
_ = require('lodash-contrib')
async = require('async')
resin = require('resin-sdk')
visuals = require('resin-cli-visuals')
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
vcs = require('resin-vcs')
exports.create =
signature: 'app create <name>'
@ -12,7 +22,7 @@ exports.create =
help: '''
Use this command to create a new resin.io application.
You can specify the application type with the `--type` option.
You can specify the application device type with the `--type` option.
Otherwise, an interactive dropdown will be shown for you to select from.
You can see a list of supported device types with
@ -28,36 +38,29 @@ exports.create =
{
signature: 'type'
parameter: 'type'
description: 'application type'
description: 'application device type (Check available types with `resin devices supported`)'
alias: 't'
}
]
permission: 'user'
action: (params, options, done) ->
async.waterfall([
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
(callback) ->
resin.models.application.has(params.name, callback)
# Validate the the application name is available
# before asking the device type.
# https://github.com/resin-io/resin-cli/issues/30
resin.models.application.has(params.name).then (hasApplication) ->
if hasApplication
throw new Error('You already have an application with that name!')
(hasApplication, callback) ->
if hasApplication
return callback(new Error('You already have an application with that name!'))
return callback(null, options.type) if options.type?
resin.models.device.getSupportedDeviceTypes (error, deviceTypes) ->
return callback(error) if error?
visuals.widgets.select('Select a type', deviceTypes, callback)
(type, callback) ->
options.type = type
resin.models.application.create(params.name, options.type, callback)
(applicationId, callback) ->
console.info("Application created: #{params.name} (#{options.type}, id #{applicationId})")
return callback()
], done)
.then ->
return options.type or patterns.selectDeviceType()
.then (deviceType) ->
return resin.models.application.create(params.name, deviceType)
.then (application) ->
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
.nodeify(done)
exports.list =
signature: 'apps'
@ -73,17 +76,20 @@ exports.list =
$ resin apps
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin.models.application.getAll (error, applications) ->
return done(error) if error?
console.log visuals.widgets.table.horizontal applications, [
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
resin.models.application.getAll().then (applications) ->
console.log visuals.table.horizontal applications, [
'id'
'app_name'
'device_type'
'online_devices'
'devices_length'
]
return done()
.nodeify(done)
exports.info =
signature: 'app <name>'
@ -96,17 +102,20 @@ exports.info =
$ resin app MyApp
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin.models.application.get params.name, (error, application) ->
return done(error) if error?
console.log visuals.widgets.table.vertical application, [
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
resin.models.application.get(params.name).then (application) ->
console.log visuals.table.vertical application, [
"$#{application.app_name}$"
'id'
'app_name'
'device_type'
'git_repository'
'commit'
]
return done()
.nodeify(done)
exports.restart =
signature: 'app restart <name>'
@ -120,7 +129,8 @@ exports.restart =
'''
permission: 'user'
action: (params, options, done) ->
resin.models.application.restart(params.name, done)
resin = require('resin-sdk-preconfigured')
resin.models.application.restart(params.name).nodeify(done)
exports.remove =
signature: 'app rm <name>'
@ -139,94 +149,9 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'application', options.yes, (callback) ->
resin.models.application.remove(params.name, callback)
, done
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
exports.associate =
signature: 'app associate <name>'
description: 'associate a resin project'
help: '''
Use this command to associate a project directory with a resin application.
This command adds a 'resin' git remote to the directory and runs git init if necessary.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin app associate MyApp
$ resin app associate MyApp --project my/app/directory
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
currentDirectory = process.cwd()
async.waterfall [
(callback) ->
resin.models.application.has(params.name, callback)
(hasApp, callback) ->
if not hasApp
return callback(new Error("Invalid application: #{params.name}"))
message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?"
visuals.patterns.confirm(options.yes, message, callback)
(confirmed, callback) ->
return done() if not confirmed
vcs.initialize(currentDirectory, callback)
(callback) ->
resin.models.application.get(params.name, callback)
(application, callback) ->
vcs.addRemote(currentDirectory, application.git_repository, callback)
], (error, remoteUrl) ->
return done(error) if error?
console.info("git repository added: #{remoteUrl}")
return done(null, remoteUrl)
exports.init =
signature: 'init'
description: 'init an application'
help: '''
Use this command to initialise a directory as a resin application.
This command performs the following steps:
- Create a resin.io application.
- Initialize the current directory as a git repository.
- Add the corresponding git remote to the application.
Examples:
$ resin init
$ resin init --project my/app/directory
'''
permission: 'user'
action: (params, options, done) ->
currentDirectory = process.cwd()
async.waterfall([
(callback) ->
currentDirectoryBasename = path.basename(currentDirectory)
visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback)
(applicationName, callback) ->
# TODO: Make resin.models.application.create return
# the whole application instead of just the id
exports.create.action name: applicationName, options, (error) ->
return callback(error) if error?
return callback(null, applicationName)
(applicationName, callback) ->
exports.associate.action(name: applicationName, options, callback)
], done)
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
resin.models.application.remove(params.name)
.nodeify(done)

View File

@ -1,62 +1,124 @@
open = require('open')
_ = require('lodash-contrib')
url = require('url')
async = require('async')
resin = require('resin-sdk')
settings = require('resin-settings-client')
visuals = require('resin-cli-visuals')
###
Copyright 2016-2017 Resin.io
TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences')
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.
###
exports.login =
signature: 'login [token]'
signature: 'login'
description: 'login to resin.io'
help: """
help: '''
Use this command to login to your resin.io account.
To login, you need your token, which is accesible from the preferences page:
This command will prompt you to login using the following login types:
#{TOKEN_URL}
- Web authorization: open your web browser and prompt you to authorize the CLI
from the dashboard.
- Credentials: using email/password and 2FA.
- Token: using the authentication token from the preferences page.
Examples:
$ resin login
$ resin login "eyJ0eXAiOiJKV1Qi..."
"""
$ resin login --web
$ resin login --token "..."
$ resin login --credentials
$ resin login --credentials --email johndoe@gmail.com --password secret
'''
options: [
{
signature: 'token'
description: 'auth token'
parameter: 'token'
alias: 't'
}
{
signature: 'web'
description: 'web-based login'
boolean: true
alias: 'w'
}
{
signature: 'credentials'
description: 'credential-based login'
boolean: true
alias: 'c'
}
{
signature: 'email'
parameter: 'email'
description: 'email'
alias: [ 'e', 'u' ]
}
{
signature: 'password'
parameter: 'password'
description: 'password'
alias: 'p'
}
]
primary: true
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
auth = require('../auth')
form = require('resin-cli-form')
patterns = require('../utils/patterns')
messages = require('../utils/messages')
async.waterfall([
login = (options) ->
if options.token?
return Promise.try ->
return options.token if _.isString(options.token)
return form.ask
message: 'Token (from the preferences page)'
name: 'token'
type: 'input'
.then(resin.auth.loginWithToken)
else if options.credentials
return patterns.authenticate(options)
else if options.web
console.info('Connecting to the web dashboard')
return auth.login()
(callback) ->
return callback(null, params.token) if params.token?
return patterns.askLoginType().then (loginType) ->
console.info """
To login to the Resin CLI, you need your unique token, which is accesible from
the preferences page at #{TOKEN_URL}
if loginType is 'register'
capitanoRunAsync = Promise.promisify(require('capitano').run)
return capitanoRunAsync('signup')
Attempting to open a browser at that location...
"""
options[loginType] = true
return login(options)
open TOKEN_URL, (error) ->
if error?
console.error """
Unable to open a web browser in the current environment.
Please visit #{TOKEN_URL} manually.
"""
resin.settings.get('resinUrl').then (resinUrl) ->
console.log(messages.resinAsciiArt)
console.log("\nLogging in to #{resinUrl}")
return login(options)
.then(resin.auth.whoami)
.tap (username) ->
console.info("Successfully logged in as: #{username}")
console.info """
visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback)
Find out about the available commands by running:
(token, callback) ->
resin.auth.loginWithToken(token, callback)
$ resin help
(callback) ->
resin.auth.whoami(callback)
(username, callback) ->
console.info("Successfully logged in as: #{username}")
return callback()
], done)
#{messages.reachingOut}
"""
.nodeify(done)
exports.logout =
signature: 'logout'
@ -70,7 +132,8 @@ exports.logout =
'''
permission: 'user'
action: (params, options, done) ->
resin.auth.logout(done)
resin = require('resin-sdk-preconfigured')
resin.auth.logout().nodeify(done)
exports.signup =
signature: 'signup'
@ -83,70 +146,41 @@ exports.signup =
Examples:
$ resin signup
Email: me@mycompany.com
Username: johndoe
Email: johndoe@acme.com
Password: ***********
$ resin signup --email me@mycompany.com --username johndoe --password ***********
$ resin whoami
johndoe
'''
options: [
{
signature: 'email'
parameter: 'email'
description: 'user email'
alias: 'e'
}
{
signature: 'username'
parameter: 'username'
description: 'user name'
alias: 'u'
}
{
signature: 'password'
parameter: 'user password'
description: 'user password'
alias: 'p'
}
]
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
form = require('resin-cli-form')
validation = require('../utils/validation')
hasOptionCredentials = not _.isEmpty(options)
resin.settings.get('resinUrl').then (resinUrl) ->
console.log("\nRegistering to #{resinUrl}")
if hasOptionCredentials
form.run [
message: 'Email:'
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Password:'
name: 'password'
type: 'password',
validate: validation.validatePassword
]
if not options.email?
return done(new Error('Missing email'))
if not options.username?
return done(new Error('Missing username'))
if not options.password?
return done(new Error('Missing password'))
async.waterfall([
(callback) ->
return callback(null, options) if hasOptionCredentials
visuals.widgets.register(callback)
(credentials, callback) ->
resin.auth.register credentials, (error, token) ->
return callback(error, credentials)
(credentials, callback) ->
resin.auth.login(credentials, callback)
], done)
.then(resin.auth.register)
.then(resin.auth.loginWithToken)
.nodeify(done)
exports.whoami =
signature: 'whoami'
description: 'get current username'
description: 'get current username and email address'
help: '''
Use this command to find out the current logged in username.
Use this command to find out the current logged in username and email address.
Examples:
@ -154,11 +188,19 @@ exports.whoami =
'''
permission: 'user'
action: (params, options, done) ->
resin.auth.whoami (error, username) ->
return done(error) if error?
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
if not username?
return done(new Error('Username not found'))
console.log(username)
return done()
Promise.props
username: resin.auth.whoami()
email: resin.auth.getEmail()
url: resin.settings.get('resinUrl')
.then (results) ->
console.log visuals.table.vertical results, [
'$account information$'
'username'
'email'
'url'
]
.nodeify(done)

64
lib/actions/build.coffee Normal file
View File

@ -0,0 +1,64 @@
# Imported here because it's needed for the setup
# of this action
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
getBundleInfo = Promise.method (options) ->
helpers = require('../utils/helpers')
if options.application?
# An application was provided
return helpers.getAppInfo(options.application)
.then (app) ->
return [app.arch, app.device_type]
else if options.arch? and options.deviceType?
return [options.arch, options.deviceType]
else
# No information, cannot do resolution
return undefined
module.exports =
signature: 'build [source]'
description: 'Build a container locally'
permission: 'user'
help: '''
Use this command to build a container with a provided docker daemon.
You must provide either an application or a device-type/architecture
pair to use the resin Dockerfile pre-processor
(e.g. Dockerfile.template -> Dockerfile).
Examples:
$ resin build
$ resin build ./source/
$ resin build --deviceType raspberrypi3 --arch armhf
$ resin build --application MyApp ./source/
$ resin build --docker '/var/run/docker.sock'
$ resin build --dockerHost my.docker.host --dockerPort 2376 --ca ca.pem --key key.pem --cert cert.pem
'''
options: dockerUtils.appendOptions [
{
signature: 'arch'
parameter: 'arch'
description: 'The architecture to build for'
alias: 'A'
},
{
signature: 'deviceType'
parameter: 'deviceType'
description: 'The type of device this build is for'
alias: 'd'
},
{
signature: 'application'
parameter: 'application'
description: 'The target resin.io application this build is for'
alias: 'a'
},
]
action: (params, options, done) ->
Logger = require('../utils/logger')
dockerUtils.runBuild(params, options, getBundleInfo, new Logger())
.asCallback(done)

View File

@ -1,3 +1,19 @@
###
Copyright 2016-2017 Resin.io
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.
###
_ = require('lodash')
exports.yes =
@ -19,15 +35,32 @@ exports.application = _.defaults
exports.optionalDevice =
signature: 'device'
parameter: 'device'
description: 'device name'
description: 'device uuid'
alias: 'd'
exports.optionalDeviceApiKey =
signature: 'deviceApiKey'
description: 'custom device key - note that this is only supported on ResinOS 2.0.3+'
parameter: 'device-api-key'
alias: 'k'
exports.booleanDevice =
signature: 'device'
description: 'device name'
description: 'device'
boolean: true
alias: 'd'
exports.osVersion =
signature: 'version'
description: """
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)
"""
parameter: 'version'
exports.network =
signature: 'network'
parameter: 'network'
@ -45,3 +78,23 @@ exports.wifiKey =
parameter: 'key'
description: 'wifi key, if network is wifi'
alias: 'k'
exports.forceUpdateLock =
signature: 'force'
description: 'force action if the update lock is set'
boolean: true
alias: 'f'
exports.drive =
signature: 'drive'
description: 'the drive to write the image to, like `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `resin util available-drives` for available options.'
parameter: 'drive'
alias: 'd'
exports.advancedConfig =
signature: 'advanced'
description: 'show advanced configuration options'
boolean: true
alias: 'v'

311
lib/actions/config.coffee Normal file
View File

@ -0,0 +1,311 @@
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
exports.read =
signature: 'config read'
description: 'read a device configuration'
help: '''
Use this command to read the config.json file from the mounted filesystem (e.g. SD card) of a provisioned device"
Examples:
$ resin config read --type raspberry-pi
$ resin config read --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
prettyjson = require('prettyjson')
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umountAsync)
.then (drive) ->
return config.read(drive, options.type)
.tap (configJSON) ->
console.info(prettyjson.render(configJSON))
.nodeify(done)
exports.write =
signature: 'config write <key> <value>'
description: 'write a device configuration'
help: '''
Use this command to write the config.json file to the mounted filesystem (e.g. SD card) of a provisioned device
Examples:
$ resin config write --type raspberry-pi username johndoe
$ resin config write --type raspberry-pi --drive /dev/disk2 username johndoe
$ resin config write --type raspberry-pi files.network/settings "..."
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).then (configJSON) ->
console.info("Setting #{params.key} to #{params.value}")
_.set(configJSON, params.key, params.value)
return configJSON
.tap ->
return umountAsync(drive)
.then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
console.info('Done')
.nodeify(done)
exports.inject =
signature: 'config inject <file>'
description: 'inject a device configuration file'
help: '''
Use this command to inject a config.json file to the mounted filesystem (e.g. SD card) of a provisioned device"
Examples:
$ resin config inject my/config.json --type raspberry-pi
$ resin config inject my/config.json --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umountAsync = Promise.promisify(require('umount').umount)
readFileAsync = Promise.promisify(require('fs').readFile)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umountAsync)
.then (drive) ->
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
console.info('Done')
.nodeify(done)
exports.reconfigure =
signature: 'config reconfigure'
description: 'reconfigure a provisioned device'
help: '''
Use this command to reconfigure a provisioned device
Examples:
$ resin config reconfigure --type raspberry-pi
$ resin config reconfigure --type raspberry-pi --advanced
$ resin config reconfigure --type raspberry-pi --drive /dev/disk2
'''
options: [
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
{
signature: 'drive'
description: 'drive'
parameter: 'drive'
alias: 'd'
}
{
signature: 'advanced'
description: 'show advanced commands'
boolean: true
alias: 'v'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
capitanoRunAsync = Promise.promisify(require('capitano').run)
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).get('uuid')
.tap ->
umountAsync(drive)
.then (uuid) ->
configureCommand = "os configure #{drive} #{uuid}"
if options.advanced
configureCommand += ' --advanced'
return capitanoRunAsync(configureCommand)
.then ->
console.info('Done')
.nodeify(done)
exports.generate =
signature: 'config generate'
description: 'generate a config.json file'
help: '''
Use this command to generate a config.json for a device or application.
This is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
Examples:
$ resin config generate --device 7cf02a6
$ resin config generate --device 7cf02a6 --device-api-key <existingDeviceKey>
$ resin config generate --device 7cf02a6 --output config.json
$ resin config generate --app MyApp
$ resin config generate --app MyApp --output config.json
$ resin config generate --app MyApp --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
'''
options: [
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
{
signature: 'output'
description: 'output'
parameter: 'output'
alias: 'o'
}
# Options for non-interactive configuration
{
signature: 'network'
description: 'the network type to use: ethernet or wifi'
parameter: 'network'
}
{
signature: 'wifiSsid'
description: 'the wifi ssid to use (used only if --network is set to wifi)'
parameter: 'wifiSsid'
}
{
signature: 'wifiKey'
description: 'the wifi key to use (used only if --network is set to wifi)'
parameter: 'wifiKey'
}
{
signature: 'appUpdatePollInterval'
description: 'how frequently (in minutes) to poll for application updates'
parameter: 'appUpdatePollInterval'
}
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
writeFileAsync = Promise.promisify(require('fs').writeFile)
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
form = require('resin-cli-form')
deviceConfig = require('resin-device-config')
prettyjson = require('prettyjson')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
if not options.device? and not options.application?
throw new Error '''
You have to pass either a device or an application.
See the help page for examples:
$ resin help config generate
'''
Promise.try ->
if options.device?
return resin.models.device.get(options.device)
return resin.models.application.get(options.application)
.then (resource) ->
resin.models.device.getManifestBySlug(resource.device_type)
.get('options')
.then (formOptions) ->
# Pass params as an override: if there is any param with exactly the same name as a
# required option, that value is used (and the corresponding question is not asked)
form.run(formOptions, override: options)
.then (answers) ->
if resource.uuid?
generateDeviceConfig(resource, options.deviceApiKey, answers)
else
generateApplicationConfig(resource, answers)
.then (config) ->
deviceConfig.validate(config)
if options.output?
return writeFileAsync(options.output, JSON.stringify(config))
console.log(prettyjson.render(config))
.nodeify(done)

229
lib/actions/deploy.coffee Normal file
View File

@ -0,0 +1,229 @@
Promise = require('bluebird')
dockerUtils = require('../utils/docker')
getBuilderPushEndpoint = (baseUrl, owner, app) ->
querystring = require('querystring')
args = querystring.stringify({ owner, app })
"https://builder.#{baseUrl}/v1/push?#{args}"
getBuilderLogPushEndpoint = (baseUrl, buildId, owner, app) ->
querystring = require('querystring')
args = querystring.stringify({ owner, app, buildId })
"https://builder.#{baseUrl}/v1/pushLogs?#{args}"
formatImageName = (image) ->
image.split('/').pop()
parseInput = Promise.method (params, options) ->
if not params.appName?
throw new Error('Need an application to deploy to!')
appName = params.appName
image = undefined
if params.image?
if options.build or options.source?
throw new Error('Build and source parameters are not applicable when specifying an image')
options.build = false
image = params.image
else if options.build
source = options.source || '.'
else
throw new Error('Need either an image or a build flag!')
return [appName, options.build, source, image]
showPushProgress = (message) ->
visuals = require('resin-cli-visuals')
progressBar = new visuals.Progress(message)
progressBar.update({ percentage: 0 })
return progressBar
getBundleInfo = (options) ->
helpers = require('../utils/helpers')
helpers.getAppInfo(options.appName)
.then (app) ->
[app.arch, app.device_type]
performUpload = (imageStream, token, username, url, appName, logger) ->
request = require('request')
progressStream = require('progress-stream')
zlib = require('zlib')
# Need to strip off the newline
progressMessage = logger.formatMessage('info', 'Deploying').slice(0, -1)
progressBar = showPushProgress(progressMessage)
streamWithProgress = imageStream.pipe progressStream
time: 500,
length: imageStream.length
, ({ percentage, eta }) ->
progressBar.update
percentage: Math.min(percentage, 100)
eta: eta
uploadRequest = request.post
url: getBuilderPushEndpoint(url, username, appName)
headers:
'Content-Encoding': 'gzip'
auth:
bearer: token
body: streamWithProgress.pipe(zlib.createGzip({
level: 6
}))
uploadToPromise(uploadRequest, logger)
uploadLogs = (logs, token, url, buildId, username, appName) ->
request = require('request')
request.post
json: true
url: getBuilderLogPushEndpoint(url, buildId, username, appName)
auth:
bearer: token
body: Buffer.from(logs)
uploadToPromise = (uploadRequest, logger) ->
new Promise (resolve, reject) ->
handleMessage = (data) ->
data = data.toString()
logger.logDebug("Received data: #{data}")
try
obj = JSON.parse(data)
catch e
logger.logError('Error parsing reply from remote side')
reject(e)
return
if obj.type?
switch obj.type
when 'error' then reject(new Error("Remote error: #{obj.error}"))
when 'success' then resolve(obj)
when 'status' then logger.logInfo("Remote: #{obj.message}")
else reject(new Error("Received unexpected reply from remote: #{data}"))
else
reject(new Error("Received unexpected reply from remote: #{data}"))
uploadRequest
.on('error', reject)
.on('data', handleMessage)
module.exports =
signature: 'deploy <appName> [image]'
description: 'Deploy an image to a resin.io application'
help: '''
Use this command to deploy an image to an application, optionally building it first.
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
To deploy to an app on which you're a collaborator, use
`resin deploy <appOwnerUsername>/<appName>`.
Note: If building with this command, all options supported by `resin build`
are also supported with this command.
Examples:
$ resin deploy myApp --build --source myBuildDir/
$ resin deploy myApp myApp/myImage
'''
permission: 'user'
options: dockerUtils.appendOptions [
{
signature: 'build'
boolean: true
description: 'Build image then deploy'
alias: 'b'
},
{
signature: 'source'
parameter: 'source'
description: 'The source directory to use when building the image'
alias: 's'
},
{
signature: 'nologupload'
description: "Don't upload build logs to the dashboard with image (if building)"
boolean: true
}
]
action: (params, options, done) ->
_ = require('lodash')
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
resin = require('resin-sdk-preconfigured')
Logger = require('../utils/logger')
logger = new Logger()
# Ensure the tmp files gets deleted
tmp.setGracefulCleanup()
logs = ''
upload = (token, username, url) ->
dockerUtils.getDocker(options)
.then (docker) ->
# Check input parameters
parseInput(params, options)
.then ([appName, build, source, imageName]) ->
tmpNameAsync()
.then (bufferFile) ->
# Setup the build args for how the build routine expects them
options = _.assign({}, options, { appName })
params = _.assign({}, params, { source })
Promise.try ->
if build
dockerUtils.runBuild(params, options, getBundleInfo, logger)
else
{ image: imageName, log: '' }
.then ({ image: imageName, log: buildLogs }) ->
logger.logInfo('Initializing deploy...')
logs = buildLogs
Promise.all [
dockerUtils.bufferImage(docker, imageName, bufferFile)
token
username
url
params.appName
logger
]
.spread(performUpload)
.finally ->
# If the file was never written to (for instance because an error
# has occured before any data was written) this call will throw an
# ugly error, just suppress it
Promise.try ->
require('mz/fs').unlink(bufferFile)
.catch(_.noop)
.tap ({ image: imageName, buildId }) ->
logger.logSuccess("Successfully deployed image: #{formatImageName(imageName)}")
return buildId
.then ({ image: imageName, buildId }) ->
if logs is '' or options.nologupload?
return ''
logger.logInfo('Uploading logs to dashboard...')
Promise.join(
logs
token
url
buildId
username
params.appName
uploadLogs
)
.return('Successfully uploaded logs')
.then (msg) ->
logger.logSuccess(msg) if msg isnt ''
.asCallback(done)
Promise.join(
resin.auth.getToken()
resin.auth.whoami()
resin.settings.get('resinUrl')
upload
)

View File

@ -1,22 +1,21 @@
fse = require('fs-extra')
capitano = require('capitano')
_ = require('lodash-contrib')
path = require('path')
async = require('async')
resin = require('resin-sdk')
visuals = require('resin-cli-visuals')
vcs = require('resin-vcs')
manager = require('resin-image-manager')
image = require('resin-image')
inject = require('resin-config-inject')
registerDevice = require('resin-register-device')
pine = require('resin-pine')
tmp = require('tmp')
###
Copyright 2016-2017 Resin.io
# Cleanup the temporary files even when an uncaught exception occurs
tmp.setGracefulCleanup()
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.
###
commandOptions = require('./command-options')
_ = require('lodash')
exports.list =
signature: 'devices'
@ -35,122 +34,75 @@ exports.list =
'''
options: [ commandOptions.optionalApplication ]
permission: 'user'
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
if options.application?
getFunction = _.partial(resin.models.device.getAllByApplication, options.application)
else
getFunction = resin.models.device.getAll
Promise.try ->
if options.application?
return resin.models.device.getAllByApplication(options.application)
return resin.models.device.getAll()
getFunction (error, devices) ->
return done(error) if error?
console.log visuals.widgets.table.horizontal devices, [
.tap (devices) ->
devices = _.map devices, (device) ->
device.uuid = device.uuid.slice(0, 7)
return device
console.log visuals.table.horizontal devices, [
'id'
'uuid'
'name'
'device_type'
'is_online'
'application_name'
'status'
'last_seen'
'is_online'
'supervisor_version'
'os_version'
'dashboard_url'
]
return done(null, devices)
.nodeify(done)
exports.info =
signature: 'device <name>'
signature: 'device <uuid>'
description: 'list a single device'
help: '''
Use this command to show information about a single device.
Examples:
$ resin device MyDevice
$ resin device 7cf02a6
'''
permission: 'user'
primary: true
action: (params, options, done) ->
resin.models.device.get params.name, (error, device) ->
return done(error) if error?
console.log visuals.widgets.table.vertical device, [
'id'
'name'
'device_type'
'is_online'
'ip_address'
'application_name'
'status'
'last_seen'
'uuid'
'commit'
'supervisor_version'
'is_web_accessible'
'note'
]
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
return done()
resin.models.device.get(params.uuid).then (device) ->
exports.remove =
signature: 'device rm <name>'
description: 'remove a device'
help: '''
Use this command to remove a device from resin.io.
resin.models.device.getStatus(device).then (status) ->
device.status = status
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin device rm MyDevice
$ resin device rm MyDevice --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'device', options.yes, (callback) ->
resin.models.device.remove(params.name, callback)
, done
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:
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
'''
permission: 'user'
action: (params, options, done) ->
resin.models.device.identify(params.uuid, done)
exports.rename =
signature: 'device rename <name> [newName]'
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:
$ resin device rename MyDevice MyPi
$ resin device rename MyDevice
'''
permission: 'user'
action: (params, options, done) ->
async.waterfall [
(callback) ->
if not _.isEmpty(params.newName)
return callback(null, params.newName)
visuals.widgets.ask('How do you want to name this device?', null, callback)
(newName, callback) ->
resin.models.device.rename(params.name, newName, callback)
], done
console.log visuals.table.vertical device, [
"$#{device.name}$"
'id'
'device_type'
'status'
'is_online'
'ip_address'
'application_name'
'last_seen'
'uuid'
'commit'
'supervisor_version'
'is_web_accessible'
'note'
'os_version'
'dashboard_url'
]
.nodeify(done)
exports.supported =
signature: 'devices supported'
@ -162,201 +114,333 @@ exports.supported =
$ resin devices supported
'''
permission: 'user'
action: (params, options, done) ->
resin.models.device.getSupportedDeviceTypes (error, devices) ->
return done(error) if error?
_.each(devices, _.unary(console.log))
done()
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
exports.await =
signature: 'device await <name>'
description: 'await for a device to become online'
resin.models.config.getDeviceTypes().then (deviceTypes) ->
console.log visuals.table.horizontal deviceTypes, [
'slug'
'name'
]
.nodeify(done)
exports.register =
signature: 'device register <application>'
description: 'register a device'
help: '''
Use this command to await for a device to become online.
Use this command to register a device to an application.
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).
Note that device api keys are only supported on ResinOS 2.0.3+
Examples:
$ resin device await MyDevice
$ resin device await MyDevice --interval 1000
$ resin device register MyApp
$ resin device register MyApp --uuid <uuid>
$ resin device register MyApp --uuid <uuid> --device-api-key <existingDeviceKey>
'''
options: [
signature: 'interval'
parameter: 'interval'
description: 'poll interval'
alias: 'i'
]
permission: 'user'
options: [
{
signature: 'uuid'
description: 'custom uuid'
parameter: 'uuid'
alias: 'u'
}
commandOptions.optionalDeviceApiKey
]
action: (params, options, done) ->
options.interval ?= 3000
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
poll = ->
resin.models.device.isOnline params.name, (error, isOnline) ->
return done(error) if error?
if isOnline
console.info("Device became online: #{params.name}")
return done()
Promise.join(
resin.models.application.get(params.application)
options.uuid ? resin.models.device.generateUniqueKey()
options.deviceApiKey ? resin.models.device.generateUniqueKey()
(application, uuid, deviceApiKey) ->
console.info("Registering to #{application.app_name}: #{uuid}")
if not options.deviceApiKey?
console.info("Using generated device api key: #{deviceApiKey}")
else
console.info("Polling device network status: #{params.name}")
setTimeout(poll, options.interval)
console.info('Using provided device api key')
return resin.models.device.register(application.id, uuid, deviceApiKey)
)
.get('uuid')
.nodeify(done)
poll()
exports.init =
signature: 'device init [device]'
description: 'initialise a device with resin os'
exports.remove =
signature: 'device rm <uuid>'
description: 'remove a device'
help: '''
Use this command to download the OS image of a certain application and write it to an SD Card.
Note that this command requires admin privileges.
If `device` is omitted, you will be prompted to select a device interactively.
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.
You can quiet the progress bar and other logging information by passing the `--quiet` boolean option.
Examples:
You need to configure the network type and other settings:
$ resin device rm 7cf02a6
$ resin device rm 7cf02a6 --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
Ethernet:
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
resin.models.device.remove(params.uuid)
.nodeify(done)
Wifi:
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.
exports.identify =
signature: 'device identify <uuid>'
description: 'identify a device with a UUID'
help: '''
Use this command to identify a device.
You can omit network related options to be asked about them interactively.
In the Raspberry Pi, the ACT led is blinked several times.
Examples:
$ resin device identify 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.identify(params.uuid).nodeify(done)
exports.reboot =
signature: 'device reboot <uuid>'
description: 'restart a device'
help: '''
Use this command to remotely reboot a device
Examples:
$ resin device reboot 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.reboot(params.uuid, options).nodeify(done)
exports.shutdown =
signature: 'device shutdown <uuid>'
description: 'shutdown a device'
help: '''
Use this command to remotely shutdown a device
Examples:
$ resin device shutdown 23c73a1
'''
options: [ commandOptions.forceUpdateLock ]
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.shutdown(params.uuid, options).nodeify(done)
exports.enableDeviceUrl =
signature: 'device public-url enable <uuid>'
description: 'enable public URL for a device'
help: '''
Use this command to enable public URL for a device
Examples:
$ resin device public-url enable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.enableDeviceUrl(params.uuid).nodeify(done)
exports.disableDeviceUrl =
signature: 'device public-url disable <uuid>'
description: 'disable public URL for a device'
help: '''
Use this command to disable public URL for a device
Examples:
$ resin device public-url disable 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.disableDeviceUrl(params.uuid).nodeify(done)
exports.getDeviceUrl =
signature: 'device public-url <uuid>'
description: 'gets the public URL of a device'
help: '''
Use this command to get the public URL of a device
Examples:
$ resin device public-url 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.getDeviceUrl(params.uuid).then (url) ->
console.log(url)
.nodeify(done)
exports.hasDeviceUrl =
signature: 'device public-url status <uuid>'
description: 'Returns true if public URL is enabled for a device'
help: '''
Use this command to determine if public URL is enabled for a device
Examples:
$ resin device public-url status 23c73a1
'''
permission: 'user'
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.device.hasDeviceUrl(params.uuid).then (hasDeviceUrl) ->
console.log(hasDeviceUrl)
.nodeify(done)
exports.rename =
signature: 'device rename <uuid> [newName]'
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:
$ resin device rename 7cf02a6
$ resin device rename 7cf02a6 MyPi
'''
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
form = require('resin-cli-form')
Promise.try ->
return params.newName if not _.isEmpty(params.newName)
form.ask
message: 'How do you want to name this device?'
type: 'input'
.then(_.partial(resin.models.device.rename, params.uuid))
.nodeify(done)
exports.move =
signature: 'device move <uuid>'
description: 'move a device to another application'
help: '''
Use this command to move a device to another application you own.
If you omit the application, you'll get asked for it interactively.
Examples:
$ resin device move 7cf02a6
$ resin device move 7cf02a6 --application MyNewApp
'''
permission: 'user'
options: [ commandOptions.optionalApplication ]
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
resin.models.device.get(params.uuid).then (device) ->
return options.application or patterns.selectApplication (application) ->
return _.every [
application.device_type is device.device_type
device.application_name isnt application.app_name
]
.tap (application) ->
return resin.models.device.move(params.uuid, application)
.then (application) ->
console.info("#{params.uuid} was moved to #{application}")
.nodeify(done)
exports.init =
signature: 'device init'
description: 'initialise a device with resinOS'
help: '''
Use this command to download the OS image of a certain application and write it to an SD Card.
Notice this command may ask for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin device init
$ 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
'''
options: [
commandOptions.optionalApplication
commandOptions.network
commandOptions.wifiSsid
commandOptions.wifiKey
commandOptions.yes
commandOptions.advancedConfig
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
commandOptions.drive
{
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
parameter: 'config'
}
]
permission: 'user'
root: true
action: (params, options, done) ->
Promise = require('bluebird')
capitanoRunAsync = Promise.promisify(require('capitano').run)
rimraf = Promise.promisify(require('rimraf'))
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
tmp.setGracefulCleanup()
networkOptions =
network: options.network
wifiSsid: options.ssid
wifiKey: options.key
resin = require('resin-sdk-preconfigured')
helpers = require('../utils/helpers')
patterns = require('../utils/patterns')
async.waterfall([
Promise.try ->
return options.application if options.application?
return patterns.selectApplication()
.then(resin.models.application.get)
.then (application) ->
(callback) ->
return callback(null, options.application) if options.application?
vcs.getApplicationName(process.cwd(), callback)
download = ->
tmpNameAsync().then (tempPath) ->
osVersion = options['os-version'] or 'default'
capitanoRunAsync("os download #{application.device_type} --output '#{tempPath}' --version #{osVersion}")
.disposer (tempPath) ->
return rimraf(tempPath)
(applicationName, callback) ->
options.application = applicationName
resin.models.application.has(options.application, callback)
Promise.using download(), (tempPath) ->
capitanoRunAsync("device register #{application.app_name}")
.then(resin.models.device.get)
.tap (device) ->
configureCommand = "os configure '#{tempPath}' #{device.uuid}"
if options.config
configureCommand += " --config '#{options.config}'"
else if options.advanced
configureCommand += ' --advanced'
capitanoRunAsync(configureCommand)
.then ->
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
if options.yes
osInitCommand += ' --yes'
if options.drive
osInitCommand += " --drive #{options.drive}"
capitanoRunAsync(osInitCommand)
# Make sure the device resource is removed if there is an
# error when configuring or initializing a device image
.catch (error) ->
resin.models.device.remove(device.uuid).finally ->
throw error
.then (device) ->
console.log('Done')
return device.uuid
(hasApplication, callback) ->
if not hasApplication
return callback(new Error("Invalid application: #{options.application}"))
return callback(null, params.device) if params.device?
visuals.patterns.selectDrive(callback)
(device, callback) ->
params.device = device
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
visuals.patterns.confirm(options.yes, message, callback)
(confirmed, callback) ->
return done() if not confirmed
return callback() if networkOptions.network?
visuals.patterns.selectNetworkParameters (error, parameters) ->
return callback(error) if error?
_.extend(networkOptions, parameters)
return callback()
(callback) ->
console.info("Checking application: #{options.application}")
resin.models.application.get(options.application, callback)
(application, callback) ->
async.parallel
manifest: (callback) ->
console.info('Getting device manifest for the application')
resin.models.device.getManifestBySlug(application.device_type, callback)
config: (callback) ->
console.info('Fetching application configuration')
resin.models.application.getConfiguration(options.application, networkOptions, callback)
, callback
(results, callback) ->
console.info('Associating the device')
registerDevice.register pine, results.config, (error, device) ->
return callback(error) if error?
# Associate a device
results.config.deviceId = device.id
results.config.uuid = device.uuid
params.uuid = results.config.uuid
return callback(null, results)
(results, callback) ->
console.info('Configuring device operating system image')
if process.env.DEBUG
console.log(results.config)
bar = new visuals.widgets.Progress('Downloading Device OS')
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)')
manager.configure results.manifest, results.config, (error, imagePath, removeCallback) ->
spinner.stop()
return callback(error, imagePath, removeCallback)
, (state) ->
if state?
bar.update(state)
else
spinner.start()
(configuredImagePath, removeCallback, callback) ->
console.info('Attempting to write operating system image to drive')
bar = new visuals.widgets.Progress('Writing Device OS')
image.write
device: params.device
image: configuredImagePath
progress: _.bind(bar.update, bar)
, (error) ->
return callback(error) if error?
return callback(null, configuredImagePath, removeCallback)
(temporalImagePath, removeCallback, callback) ->
console.info('Image written successfully')
removeCallback(callback)
(callback) ->
resin.models.device.getByUUID(params.uuid, callback)
(device, callback) ->
console.info("Device created: #{device.name}")
return callback(null, device.name)
], done)
.nodeify(done)

View File

@ -1,32 +0,0 @@
_ = require('lodash')
async = require('async')
visuals = require('resin-cli-visuals')
drivelist = require('drivelist')
exports.list =
signature: 'drives'
description: 'list available drives'
help: '''
Use this command to list all drives that are connected to your machine.
Examples:
$ resin drives
'''
permission: 'user'
action: (params, options, done) ->
drivelist.list (error, drives) ->
return done(error) if error?
async.reject drives, drivelist.isSystem, (removableDrives) ->
if _.isEmpty(removableDrives)
return done(new Error('No removable devices available'))
console.log visuals.widgets.table.horizontal removableDrives, [
'device'
'description'
'size'
]
return done()

View File

@ -1,7 +1,19 @@
async = require('async')
_ = require('lodash-contrib')
resin = require('resin-sdk')
visuals = require('resin-cli-visuals')
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
exports.list =
@ -19,7 +31,7 @@ exports.list =
$ resin envs --application MyApp
$ resin envs --application MyApp --verbose
$ resin envs --device MyDevice
$ resin envs --device 7cf02a6
'''
options: [
commandOptions.optionalApplication
@ -34,31 +46,32 @@ exports.list =
]
permission: 'user'
action: (params, options, done) ->
async.waterfall([
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
(callback) ->
if options.application?
resin.models.environmentVariables.getAllByApplication(options.application, callback)
else if options.device?
resin.models.environmentVariables.device.getAll(options.device, callback)
else
return callback(new Error('You must specify an application or device'))
Promise.try ->
if options.application?
return resin.models.environmentVariables.getAllByApplication(options.application)
else if options.device?
return resin.models.environmentVariables.device.getAll(options.device)
else
throw new Error('You must specify an application or device')
(environmentVariables, callback) ->
.tap (environmentVariables) ->
if _.isEmpty(environmentVariables)
throw new Error('No environment variables found')
if not options.verbose
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
if not options.verbose
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
console.log visuals.widgets.table.horizontal environmentVariables, [
'id'
'name'
'value'
]
return callback()
], done)
console.log visuals.table.horizontal environmentVariables, [
'id'
'name'
'value'
]
.nodeify(done)
exports.remove =
signature: 'env rm <id>'
@ -85,12 +98,15 @@ exports.remove =
]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'environment variable', options.yes, (callback) ->
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
if options.device
resin.models.environmentVariables.device.remove(params.id, callback)
resin.models.environmentVariables.device.remove(params.id)
else
resin.models.environmentVariables.remove(params.id, callback)
, done
resin.models.environmentVariables.remove(params.id)
.nodeify(done)
exports.add =
signature: 'env add <key> [value]'
@ -111,7 +127,7 @@ exports.add =
$ resin env add EDITOR vim --application MyApp
$ resin env add TERM --application MyApp
$ resin env add EDITOR vim --device MyDevice
$ resin env add EDITOR vim --device 7cf02a6
'''
options: [
commandOptions.optionalApplication
@ -119,20 +135,25 @@ exports.add =
]
permission: 'user'
action: (params, options, done) ->
if not params.value?
params.value = process.env[params.key]
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
Promise.try ->
if not params.value?
return done(new Error("Environment value not found for key: #{params.key}"))
else
console.info("Warning: using #{params.key}=#{params.value} from host environment")
params.value = process.env[params.key]
if options.application?
resin.models.environmentVariables.create(options.application, params.key, params.value, done)
else if options.device?
resin.models.environmentVariables.device.create(options.device, params.key, params.value, done)
else
return done(new Error('You must specify an application or device'))
if not params.value?
throw new Error("Environment value not found for key: #{params.key}")
else
console.info("Warning: using #{params.key}=#{params.value} from host environment")
if options.application?
resin.models.environmentVariables.create(options.application, params.key, params.value)
else if options.device?
resin.models.environmentVariables.device.create(options.device, params.key, params.value)
else
throw new Error('You must specify an application or device')
.nodeify(done)
exports.rename =
signature: 'env rename <id> <value>'
@ -150,7 +171,12 @@ exports.rename =
permission: 'user'
options: [ commandOptions.booleanDevice ]
action: (params, options, done) ->
if options.device
resin.models.environmentVariables.device.update(params.id, params.value, done)
else
resin.models.environmentVariables.update(params.id, params.value, done)
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
Promise.try ->
if options.device
resin.models.environmentVariables.device.update(params.id, params.value)
else
resin.models.environmentVariables.update(params.id, params.value)
.nodeify(done)

View File

@ -1,95 +0,0 @@
mkdirp = require('mkdirp')
async = require('async')
fs = require('fs')
path = require('path')
_ = require('lodash')
resin = require('resin-sdk')
visuals = require('resin-cli-visuals')
vcs = require('resin-vcs')
examplesData = require('../data/examples.json')
exports.list =
signature: 'examples'
description: 'list all example applications'
help: '''
Use this command to list available example applications from resin.io
Example:
$ resin examples
'''
permission: 'user'
action: ->
examplesData = _.map examplesData, (example, index) ->
example.id = index + 1
return example
examplesData = _.map examplesData, (example) ->
example.author ?= 'Unknown'
return example
console.log visuals.widgets.table.horizontal examplesData, [
'id'
'name'
'display_name'
'repository'
'author'
]
exports.info =
signature: 'example <name>'
description: 'list a single example application'
help: '''
Use this command to show information of a single example application
Example:
$ resin example cimon
'''
permission: 'user'
action: (params, options, done) ->
example = _.findWhere(examplesData, name: params.name)
if not example?
return done(new Error("Unknown example: #{params.name}"))
example.author ?= 'Unknown'
console.log visuals.widgets.table.vertical example, [
'name'
'display_name'
'description'
'author'
'repository'
]
return done()
exports.clone =
signature: 'example clone <name>'
description: 'clone an example application'
help: '''
Use this command to clone an example application to the current directory
This command outputs information about the cloning process.
Use `--quiet` to remove that output.
Example:
$ resin example clone cimon
'''
permission: 'user'
action: (params, options, done) ->
example = _.findWhere(examplesData, name: params.name)
if not example?
return done(new Error("Unknown example: #{params.name}"))
currentDirectory = process.cwd()
destination = path.join(currentDirectory, example.name)
mkdirp destination, (error) ->
return done(error) if error?
console.info("Cloning #{example.display_name} to #{destination}")
vcs.clone(example.repository, destination, done)
return done()

View File

@ -1,115 +1,101 @@
###
Copyright 2016-2017 Resin.io
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.
###
_ = require('lodash')
_.str = require('underscore.string')
resin = require('resin-sdk')
capitano = require('capitano')
columnify = require('columnify')
messages = require('../utils/messages')
# TODO: Refactor this terrible mess
parse = (object) ->
return _.fromPairs _.map object, (item) ->
PADDING_INITIAL = ' '
PADDING_MIDDLE = '\t'
# Hacky way to determine if an object is
# a function or a command
if item.alias?
signature = item.toString()
else
signature = item.signature.toString()
getFieldMaxLength = (array, field) ->
return _.max _.map array, (item) ->
return item[field].toString().length
return [
signature
item.description
]
buildHelpString = (firstColumn, secondColumn) ->
result = "#{PADDING_INITIAL}#{firstColumn}"
result += "#{PADDING_MIDDLE}#{secondColumn}"
return result
indent = (text) ->
text = _.map text.split('\n'), (line) ->
return ' ' + line
return text.join('\n')
addOptionPrefix = (option) ->
return if option.length <= 0
if option.length is 1
return "-#{option}"
else
return "--#{option}"
print = (data) ->
console.log indent columnify data,
showHeaders: false
minWidth: 35
addAlias = (alias) ->
return ", #{addOptionPrefix(alias)}"
buildOptionSignatureHelp = (option) ->
result = addOptionPrefix(option.signature.toString())
if _.isString(option.alias)
result += addAlias(option.alias)
else if _.isArray(option.alias)
for alias in option.alias
result += addAlias(alias)
if option.parameter?
result += " <#{option.parameter}>"
return result
getCommandHelp = (command) ->
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature')
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ')
return buildHelpString(commandSignature, command.description)
getOptionsParsedSignatures = (optionsHelp) ->
maxLength = _.max _.map optionsHelp, (signature) ->
return signature.length
return _.map optionsHelp, (signature) ->
return _.str.rpad(signature, maxLength, ' ')
getOptionHelp = (option, maxLength) ->
result = PADDING_INITIAL
result += _.str.rpad(option.signature, maxLength, ' ')
result += PADDING_MIDDLE
result += option.description
return result
general = ->
general = (params, options, done) ->
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
console.log('Commands:\n')
console.log(messages.reachingOut)
console.log('\nPrimary commands:\n')
for command in capitano.state.commands
continue if command.isWildcard()
console.log(getCommandHelp(command))
# We do not want the wildcard command
# to be printed in the help screen.
commands = _.reject capitano.state.commands, (command) ->
return command.hidden or command.isWildcard()
console.log('\nGlobal Options:\n')
groupedCommands = _.groupBy commands, (command) ->
if command.plugin
return 'plugins'
if command.primary
return 'primary'
return 'secondary'
options = _.map capitano.state.globalOptions, (option) ->
option.signature = buildOptionSignatureHelp(option)
return option
print(parse(groupedCommands.primary))
optionSignatureMaxLength = _.max _.map options, (option) ->
return option.signature.length
if options.verbose
if not _.isEmpty(groupedCommands.plugins)
console.log('\nInstalled plugins:\n')
print(parse(groupedCommands.plugins))
for option in options
console.log(getOptionHelp(option, optionSignatureMaxLength))
console.log('\nAdditional commands:\n')
print(parse(groupedCommands.secondary))
else
console.log('\nRun `resin help --verbose` to list additional commands')
console.log()
if not _.isEmpty(capitano.state.globalOptions)
console.log('\nGlobal Options:\n')
print(parse(capitano.state.globalOptions))
return done()
command = (params, options, done) ->
capitano.state.getMatchCommand params.command, (error, command) ->
return done(error) if error?
if not command? or command.isWildcard()
return capitano.defaults.actions.commandNotFound(params.command)
return done(new Error("Command not found: #{params.command}"))
console.log("Usage: #{command.signature}")
if command.help?
console.log("\n#{command.help}")
else if command.description?
console.log("\n#{_.str.humanize(command.description)}")
console.log("\n#{_.capitalize(command.description)}")
if not _.isEmpty(command.options)
console.log('\nOptions:\n')
options = _.map command.options, (option) ->
option.signature = buildOptionSignatureHelp(option)
return option
optionSignatureMaxLength = _.max _.map options, (option) ->
return option.signature.toString().length
for option in options
console.log(getOptionHelp(option, optionSignatureMaxLength))
console.log()
print(parse(command.options))
return done()
@ -124,6 +110,13 @@ exports.help =
$ resin help apps
$ resin help os download
'''
primary: true
options: [
signature: 'verbose'
description: 'show additional commands'
boolean: true
alias: 'v'
]
action: (params, options, done) ->
if params.command?
command(params, options, done)

View File

@ -1,15 +1,38 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports =
wizard: require('./wizard')
app: require('./app')
info: require('./info')
auth: require('./auth')
drive: require('./drive')
device: require('./device')
env: require('./environment-variables')
keys: require('./keys')
logs: require('./logs')
local: require('./local')
notes: require('./notes')
preferences: require('./preferences')
help: require('./help')
examples: require('./examples')
plugin: require('./plugin')
update: require('./update')
os: require('./os')
settings: require('./settings')
config: require('./config')
sync: require('./sync')
ssh: require('./ssh')
internal: require('./internal')
build: require('./build')
deploy: require('./deploy')
util: require('./util')
preload: require('./preload')

View File

@ -1,4 +1,18 @@
packageJSON = require('../../package.json')
###
Copyright 2016-2017 Resin.io
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.
###
exports.version =
signature: 'version'
@ -7,5 +21,6 @@ exports.version =
Display the Resin CLI version.
'''
action: (params, options, done) ->
packageJSON = require('../../package.json')
console.log(packageJSON.version)
return done()

View File

@ -0,0 +1,37 @@
###
Copyright 2016-2017 Resin.io
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.
###
# These are internal commands we want to be runnable from the outside
# One use-case for this is spawning the minimal operation with root priviledges
exports.osInit =
signature: 'internal osinit <image> <type> <config>'
description: 'do actual init of the device with the preconfigured os image'
help: '''
Don't use this command directly! Use `resin os initialize <image>` instead.
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
init = require('resin-device-init')
helpers = require('../utils/helpers')
return Promise.try ->
config = JSON.parse(params.config)
init.initialize(params.image, params.type, config)
.then(helpers.osProgressHandler)
.nodeify(done)

View File

@ -1,10 +1,19 @@
_ = require('lodash')
_.str = require('underscore.string')
async = require('async')
fs = require('fs')
resin = require('resin-sdk')
capitano = require('capitano')
visuals = require('resin-cli-visuals')
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
exports.list =
@ -19,12 +28,15 @@ exports.list =
'''
permission: 'user'
action: (params, options, done) ->
resin.models.key.getAll (error, keys) ->
return done(error) if error?
console.log visuals.widgets.table.horizontal keys, [ 'id', 'title' ]
return done()
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
SSH_KEY_WIDTH = 43
resin.models.key.getAll().then (keys) ->
console.log visuals.table.horizontal keys, [
'id'
'title'
]
.nodeify(done)
exports.info =
signature: 'key <id>'
@ -38,13 +50,20 @@ exports.info =
'''
permission: 'user'
action: (params, options, done) ->
resin.models.key.get params.id, (error, key) ->
return done(error) if error?
resin = require('resin-sdk-preconfigured')
visuals = require('resin-cli-visuals')
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH)
resin.models.key.get(params.id).then (key) ->
console.log visuals.table.vertical key, [
'id'
'title'
]
console.log(visuals.widgets.table.vertical(key, [ 'id', 'title', 'public_key' ]))
return done()
# Since the public key string is long, it might
# wrap to lines below, causing the table layout to break.
# See https://github.com/resin-io/resin-cli/issues/151
console.log('\n' + key.public_key)
.nodeify(done)
exports.remove =
signature: 'key rm <id>'
@ -63,9 +82,12 @@ exports.remove =
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'key', options.yes, (callback) ->
resin.models.key.remove(params.id, callback)
, done
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
resin.models.key.remove(params.id)
.nodeify(done)
exports.add =
signature: 'key add <name> [path]'
@ -83,16 +105,19 @@ exports.add =
'''
permission: 'user'
action: (params, options, done) ->
async.waterfall [
_ = require('lodash')
Promise = require('bluebird')
readFileAsync = Promise.promisify(require('fs').readFile)
capitano = require('capitano')
resin = require('resin-sdk-preconfigured')
(callback) ->
if params.path?
fs.readFile(params.path, encoding: 'utf8', callback)
else
capitano.utils.getStdin (data) ->
return callback(null, data)
Promise.try ->
return readFileAsync(params.path, encoding: 'utf8') if params.path?
(key, callback) ->
resin.models.key.create(params.name, key, callback)
# TODO: should this be promisified for consistency?
Promise.fromNode (callback) ->
capitano.utils.getStdin (data) ->
return callback(null, data)
], done
.then(_.partial(resin.models.key.create, params.name))
.nodeify(done)

View File

@ -0,0 +1,61 @@
Promise = require('bluebird')
_ = require('lodash')
Docker = require('docker-toolbelt')
form = require('resin-cli-form')
chalk = require('chalk')
exports.dockerPort = dockerPort = 2375
exports.dockerTimeout = dockerTimeout = 2000
exports.filterOutSupervisorContainer = filterOutSupervisorContainer = (container) ->
for name in container.Names
return false if name.includes('resin_supervisor')
return true
exports.selectContainerFromDevice = Promise.method (deviceIp, filterSupervisor = false) ->
docker = new Docker(host: deviceIp, port: dockerPort, timeout: dockerTimeout)
# List all containers, including those not running
docker.listContainersAsync(all: true)
.filter (container) ->
return true if not filterSupervisor
filterOutSupervisorContainer(container)
.then (containers) ->
if _.isEmpty(containers)
throw new Error("No containers found in #{deviceIp}")
return form.ask
message: 'Select a container'
type: 'list'
choices: _.map containers, (container) ->
containerName = container.Names[0] or 'Untitled'
shortContainerId = ('' + container.Id).substr(0, 11)
containerStatus = container.Status
return {
name: "#{containerName} (#{shortContainerId}) - #{containerStatus}"
value: container.Id
}
exports.pipeContainerStream = Promise.method ({ deviceIp, name, outStream, follow = false }) ->
docker = new Docker(host: deviceIp, port: dockerPort)
container = docker.getContainer(name)
container.inspectAsync()
.then (containerInfo) ->
return containerInfo?.State?.Running
.then (isRunning) ->
container.attachAsync
logs: not follow or not isRunning
stream: follow and isRunning
stdout: true
stderr: true
.then (containerStream) ->
containerStream.pipe(outStream)
.catch (err) ->
err = '' + err.statusCode
if err is '404'
return console.log(chalk.red.bold("Container '#{name}' not found."))
throw err
exports.getSubShellCommand = require('../../utils/helpers').getSubShellCommand

View File

@ -0,0 +1,228 @@
###
Copyright 2017 Resin.io
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.
###
BOOT_PARTITION = { primary: 1 }
CONNECTIONS_FOLDER = '/system-connections'
getConfigurationSchema = (connnectionFileName = 'resin-wifi') ->
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)
partition: BOOT_PARTITION
config_json:
type: 'json'
location:
path: 'config.json'
partition: BOOT_PARTITION
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) ->
answers.advancedSettings
}
{
message: 'Do you want to enable persistent logging?'
type: 'confirm'
name: 'persistentLogging'
default: data.persistentLogging
when: (answers) ->
answers.advancedSettings
}
]
getConfiguration = (data) ->
_ = require('lodash')
inquirer = require('inquirer')
# `persistentLogging` can be `undefined`, so we want
# to make sure that case defaults to `false`
data = _.assign data,
persistentLogging: data.persistentLogging or false
inquirer.prompt(inquirerOptions(data))
.then (answers) ->
return _.merge(data, answers)
# Taken from https://goo.gl/kr1kCt
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
###
prepareConnectionFile = (target) ->
_ = require('lodash')
imagefs = require('resin-image-fs')
imagefs.listDirectory
image: target
partition: BOOT_PARTITION
path: CONNECTIONS_FOLDER
.then (files) ->
# The required file already exists
if _.includes(files, 'resin-wifi')
return null
# Fresh image, new mode, accoding to https://github.com/resin-os/meta-resin/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) ->
return getConfigurationSchema(connectionFileName)
removeHostname = (schema) ->
_ = require('lodash')
schema.mapper = _.reject schema.mapper, (mapper) ->
_.isEqual(Object.keys(mapper.template), ['hostname'])
module.exports =
signature: 'local configure <target>'
description: '(Re)configure a resinOS drive or image'
help: '''
Use this command to configure or reconfigure a resinOS drive or image.
Examples:
$ resin local configure /dev/sdc
$ resin local configure path/to/image.img
'''
root: true
action: (params, options, done) ->
Promise = require('bluebird')
umount = require('umount')
umountAsync = Promise.promisify(umount.umount)
isMountedAsync = Promise.promisify(umount.isMounted)
reconfix = require('reconfix')
denymount = Promise.promisify(require('denymount'))
prepareConnectionFile(params.target)
.tap ->
isMountedAsync(params.target).then (isMounted) ->
return if not isMounted
umountAsync(params.target)
.then (configurationSchema) ->
denymount params.target, (cb) ->
reconfix.readConfiguration(configurationSchema, params.target)
.then(getConfiguration)
.then (answers) ->
if not answers.hostname
removeHostname(configurationSchema)
reconfix.writeConfiguration(configurationSchema, answers, params.target)
.asCallback(cb)
.then ->
console.log('Done!')
.asCallback(done)

View File

@ -0,0 +1,120 @@
###
Copyright 2017 Resin.io
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.
###
module.exports =
signature: 'local flash <image>'
description: 'Flash an image to a drive'
help: '''
Use this command to flash a resinOS image to a drive.
Examples:
$ resin local flash path/to/resinos.img
$ resin local flash path/to/resinos.img --drive /dev/disk2
$ resin local flash path/to/resinos.img --drive /dev/disk2 --yes
'''
options: [
signature: 'yes'
boolean: true
description: 'confirm non-interactively'
alias: 'y'
,
signature: 'drive'
parameter: 'drive'
description: 'drive'
alias: 'd'
]
root: true
action: (params, options, done) ->
_ = require('lodash')
os = require('os')
Promise = require('bluebird')
umountAsync = Promise.promisify(require('umount').umount)
fs = Promise.promisifyAll(require('fs'))
driveListAsync = Promise.promisify(require('drivelist').list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
imageWrite = require('etcher-image-write')
form.run [
{
message: 'Select drive'
type: 'drive'
name: 'drive'
},
{
message: 'This will erase the selected drive. Are you sure?'
type: 'confirm'
name: 'yes'
default: false
}
],
override:
drive: options.drive
# If `options.yes` is `false`, pass `undefined`,
# otherwise the question will not be asked because
# `false` is a defined value.
yes: options.yes || undefined
# TODO: dedupe with the resin-device-operations
.then (answers) ->
if answers.yes isnt true
console.log(chalk.red.bold('Aborted image flash'))
process.exit(0)
driveListAsync().then (drives) ->
selectedDrive = _.find(drives, device: answers.drive)
if not selectedDrive?
throw new Error("Drive not found: #{answers.drive}")
return selectedDrive
.then (selectedDrive) ->
progressBars =
write: new visuals.Progress('Flashing')
check: new visuals.Progress('Validating')
umountAsync(selectedDrive.device).then ->
Promise.props
imageSize: fs.statAsync(params.image).get('size'),
imageStream: Promise.resolve(fs.createReadStream(params.image))
driveFileDescriptor: fs.openAsync(selectedDrive.raw, 'rs+')
.then (results) ->
imageWrite.write
fd: results.driveFileDescriptor
device: selectedDrive.raw
size: selectedDrive.size
,
stream: results.imageStream,
size: results.imageSize
,
check: true
.then (writer) ->
new Promise (resolve, reject) ->
writer.on 'progress', (state) ->
progressBars[state.type].update(state)
writer.on('error', reject)
writer.on('done', resolve)
.then ->
if (os.platform() is 'win32') and selectedDrive.mountpoint?
ejectAsync = Promise.promisify(require('removedrive').eject)
return ejectAsync(selectedDrive.mountpoint)
return umountAsync(selectedDrive.device)
.asCallback(done)

View File

@ -0,0 +1,23 @@
###
Copyright 2017 Resin.io
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.
###
exports.configure = require('./configure')
exports.flash = require('./flash')
exports.logs = require('./logs')
exports.scan = require('./scan')
exports.ssh = require('./ssh')
exports.push = require('./push')
exports.stop = require('./stop')

View File

@ -0,0 +1,66 @@
###
Copyright 2017 Resin.io
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.
###
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
#
module.exports =
signature: 'local logs [deviceIp]'
description: 'Get or attach to logs of a running container on a resinOS device'
help: '''
Examples:
$ resin local logs
$ resin local logs -f
$ resin local logs 192.168.1.10
$ resin local logs 192.168.1.10 -f
$ resin local logs 192.168.1.10 -f --app-name myapp
'''
options: [
signature: 'follow'
boolean: true
description: 'follow log'
alias: 'f'
,
signature: 'app-name'
parameter: 'name'
description: 'name of container to get logs from'
alias: 'a'
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
{ forms } = require('resin-sync')
{ selectContainerFromDevice, pipeContainerStream } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (@deviceIp) =>
if not options['app-name']?
return selectContainerFromDevice(@deviceIp)
return options['app-name']
.then (appName) =>
pipeContainerStream
deviceIp: @deviceIp
name: appName
outStream: process.stdout
follow: options['follow']

View File

@ -0,0 +1,76 @@
###
Copyright 2016-2017 Resin.io
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.
###
# Loads '.resin-sync.yml' configuration from 'source' directory.
# Returns the configuration object on success
#
_ = require('lodash')
resinPush = require('resin-sync').capitano('resin-toolbox')
# TODO: This is a temporary workaround to reuse the existing `rdt push`
# capitano frontend in `resin local push`.
resinPushHelp = '''
Warning: 'resin local push' requires an openssh-compatible client and 'rsync' to
be correctly installed in your shell environment. For more information (including
Windows support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to push your local changes to a container on a LAN-accessible resinOS device on the fly.
If `Dockerfile` or any file in the 'build-triggers' list is changed,
a new container will be built and run on your device.
If not, changes will simply be synced with `rsync` into the application container.
After every 'resin local push' the updated settings will be saved in
'<source>/.resin-sync.yml' and will be used in later invocations. You can
also change any option by editing '.resin-sync.yml' directly.
Here is an example '.resin-sync.yml' :
$ cat $PWD/.resin-sync.yml
destination: '/usr/src/app'
before: 'echo Hello'
after: 'echo Done'
ignore:
- .git
- node_modules/
Command line options have precedence over the ones saved in '.resin-sync.yml'.
If '.gitignore' is found in the source directory then all explicitly listed files will be
excluded when using rsync to update the container. You can choose to change this default behavior with the
'--skip-gitignore' option.
Examples:
$ resin local push
$ resin local push --app-name test-server --build-triggers package.json,requirements.txt
$ resin local push --force-build
$ resin local push --force-build --skip-logs
$ resin local push --ignore lib/
$ resin local push --verbose false
$ resin local push 192.168.2.10 --source . --destination /usr/src/app
$ resin local push 192.168.2.10 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
'''
module.exports = _.assign resinPush,
signature: 'local push [deviceIp]'
help: resinPushHelp
primary: true
root: true

View File

@ -0,0 +1,99 @@
###
Copyright 2017 Resin.io
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.
###
dockerInfoProperties = [
'Containers'
'ContainersRunning'
'ContainersPaused'
'ContainersStopped'
'Images'
'Driver'
'SystemTime'
'KernelVersion'
'OperatingSystem'
'Architecture'
]
dockerVersionProperties = [
'Version'
'ApiVersion'
]
module.exports =
signature: 'local scan'
description: 'Scan for resinOS devices in your local network'
help: '''
Examples:
$ resin local scan
$ resin local scan --timeout 120
$ resin local scan --verbose
'''
options: [
signature: 'verbose'
boolean: true
description: 'Display full info'
alias: 'v'
,
signature: 'timeout'
parameter: 'timeout'
description: 'Scan timeout in seconds'
alias: 't'
]
primary: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
prettyjson = require('prettyjson')
Docker = require('docker-toolbelt')
{ discover } = require('resin-sync')
{ SpinnerPromise } = require('resin-cli-visuals')
{ dockerPort, dockerTimeout } = require('./common')
if options.timeout?
options.timeout *= 1000
Promise.try ->
new SpinnerPromise
promise: discover.discoverLocalResinOsDevices(options.timeout)
startMessage: 'Scanning for local resinOS devices..'
stopMessage: 'Reporting scan results'
.filter ({ address }) ->
Promise.try ->
docker = new Docker(host: address, port: dockerPort, timeout: dockerTimeout)
docker.pingAsync()
.return(true)
.catchReturn(false)
.tap (devices) ->
if _.isEmpty(devices)
throw new Error('Could not find any resinOS devices in the local network')
.map ({ host, address }) ->
docker = new Docker(host: address, port: dockerPort, timeout: dockerTimeout)
Promise.props
dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info')
dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version')
.then ({ dockerInfo, dockerVersion }) ->
if not options.verbose
dockerInfo = _.pick(dockerInfo, dockerInfoProperties) if _.isObject(dockerInfo)
dockerVersion = _.pick(dockerVersion, dockerVersionProperties) if _.isObject(dockerVersion)
return { host, address, dockerInfo, dockerVersion }
.then (devicesInfo) ->
console.log(prettyjson.render(devicesInfo, noColor: true))
.nodeify(done)

View File

@ -0,0 +1,109 @@
###
Copyright 2017 Resin.io
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.
###
module.exports =
signature: 'local ssh [deviceIp]'
description: 'Get a shell into a resinOS device'
help: '''
Warning: 'resin local ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to get a shell into the running application container of
your device.
The '--host' option will get you a shell into the Host OS of the resinOS device.
No option will return a list of containers to enter or you can explicitly select
one by passing its name to the --container option
Examples:
$ resin local ssh
$ resin local ssh --host
$ resin local ssh --container chaotic_water
$ resin local ssh --container chaotic_water --port 22222
$ resin local ssh --verbose
'''
options: [
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
,
signature: 'host'
boolean: true
description: 'get a shell into the host OS'
alias: 's'
,
signature: 'container'
parameter: 'container'
default: null
description: 'name of container to access'
alias: 'c'
,
signature: 'port'
parameter: 'port'
description: 'ssh port number (default: 22222)'
alias: 'p'
]
root: true
action: (params, options, done) ->
child_process = require('child_process')
Promise = require 'bluebird'
_ = require('lodash')
{ forms } = require('resin-sync')
{ selectContainerFromDevice, getSubShellCommand } = require('./common')
if (options.host is true and options.container?)
throw new Error('Please pass either --host or --container option')
if not options.port?
options.port = 22222
verbose = if options.verbose then '-vvv' else ''
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (deviceIp) ->
_.assign(options, { deviceIp })
return if options.host
if not options.container?
return selectContainerFromDevice(deviceIp)
return options.container
.then (container) ->
command = "ssh \
#{verbose} \
-t \
-p #{options.port} \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
root@#{options.deviceIp}"
if not options.host
shellCmd = '''/bin/sh -c $"'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi'"'''
command += " docker exec -ti #{container} #{shellCmd}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)

View File

@ -0,0 +1,79 @@
###
Copyright 2017 Resin.io
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.
###
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin` should be encapsulated in a
# re-usable package.
#
module.exports =
signature: 'local stop [deviceIp]'
description: 'Stop a running container on a resinOS device'
help: '''
Examples:
$ resin local stop
$ resin local stop --app-name myapp
$ resin local stop --all
$ resin local stop 192.168.1.10
$ resin local stop 192.168.1.10 --app-name myapp
'''
options: [
signature: 'all'
boolean: true
description: 'stop all containers'
,
signature: 'app-name'
parameter: 'name'
description: 'name of container to stop'
alias: 'a'
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
chalk = require('chalk')
{ forms, config, ResinLocalDockerUtils } = require('resin-sync')
{ selectContainerFromDevice, filterOutSupervisorContainer } = require('./common')
Promise.try ->
if not params.deviceIp?
return forms.selectLocalResinOsDevice()
return params.deviceIp
.then (@deviceIp) =>
@docker = new ResinLocalDockerUtils(@deviceIp)
if options.all
# Only list running containers
return @docker.docker.listContainersAsync(all: false)
.filter(filterOutSupervisorContainer)
.then (containers) =>
Promise.map containers, ({ Names, Id }) =>
console.log(chalk.yellow.bold("* Stopping container #{Names[0]}"))
@docker.stopContainer(Id)
ymlConfig = config.load()
@appName = options['app-name'] ? ymlConfig['local_resinos']?['app-name']
@docker.checkForRunningContainer(@appName)
.then (isRunning) =>
if not isRunning
return selectContainerFromDevice(@deviceIp, true)
console.log(chalk.yellow.bold("* Stopping container #{@appName}"))
return @appName
.then (runningContainerName) =>
@docker.stopContainer(runningContainerName)

View File

@ -1,7 +1,18 @@
_ = require('lodash')
resin = require('resin-sdk')
###
Copyright 2016-2017 Resin.io
LOGS_HISTORY_COUNT = 200
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.
###
module.exports =
signature: 'logs <uuid>'
@ -11,9 +22,6 @@ module.exports =
By default, the command prints all log messages and exit.
To limit the output to the n last lines, use the `--num` option along with a number.
This is similar to doing `resin logs <uuid> | tail -n X`.
To continuously stream output, and see new logs in real time, use the `--tail` option.
Note that for now you need to provide the whole UUID for this command to work correctly.
@ -22,17 +30,10 @@ module.exports =
Examples:
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail
$ resin logs 23c73a1
$ resin logs 23c73a1
'''
options: [
{
signature: 'num'
parameter: 'num'
description: 'number of lines to display'
alias: 'n'
}
{
signature: 'tail'
description: 'continuously stream output'
@ -41,15 +42,28 @@ module.exports =
}
]
permission: 'user'
primary: true
action: (params, options, done) ->
resin.logs.subscribe params.uuid, {
history: options.num or LOGS_HISTORY_COUNT
tail: options.tail
}, (error, message) ->
return done(error) if error?
if _.isArray(message)
_.each message, (line) ->
console.log(line.message)
else
console.log(message.message)
return done()
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
moment = require('moment')
printLine = (line) ->
timestamp = moment(line.timestamp).format('DD.MM.YY HH:mm:ss (ZZ)')
console.log("#{timestamp} #{line.message}")
promise = resin.logs.history(params.uuid).each(printLine)
if not options.tail
# PubNub keeps the process alive after a history query.
# Until this is fixed, we force the process to exit.
# This of course prevents this command to be used programatically
return promise.catch(done).finally ->
process.exit(0)
promise.then ->
resin.logs.subscribe(params.uuid).then (logs) ->
logs.on('line', printLine)
logs.on('error', done)
.catch(done)

View File

@ -1,6 +1,18 @@
_ = require('lodash')
async = require('async')
resin = require('resin-sdk')
###
Copyright 2016-2017 Resin.io
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.
###
exports.set =
signature: 'note <|note>'
@ -10,24 +22,29 @@ exports.set =
If note command isn't passed, the tool attempts to read from `stdin`.
To view the notes, use $ resin device <name>.
To view the notes, use $ resin device <uuid>.
Examples:
$ resin note "My useful note" --device MyDevice
$ cat note.txt | resin note --device MyDevice
$ resin note "My useful note" --device 7cf02a6
$ cat note.txt | resin note --device 7cf02a6
'''
options: [
signature: 'device'
parameter: 'device'
description: 'device name'
description: 'device uuid'
alias: [ 'd', 'dev' ]
required: 'You have to specify a device'
]
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
_ = require('lodash')
resin = require('resin-sdk-preconfigured')
if _.isEmpty(params.note)
return done(new Error('Missing note content'))
Promise.try ->
if _.isEmpty(params.note)
throw new Error('Missing note content')
resin.models.device.note(options.device, params.note, done)
resin.models.device.note(options.device, params.note)
.nodeify(done)

372
lib/actions/os.coffee Normal file
View File

@ -0,0 +1,372 @@
###
Copyright 2016-2017 Resin.io
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.
###
commandOptions = require('./command-options')
_ = require('lodash')
formatVersion = (v, isRecommended) ->
result = "v#{v}"
if isRecommended
result += ' (recommended)'
return result
resolveVersion = (deviceType, version) ->
if version isnt 'menu'
if version[0] == 'v'
version = version.slice(1)
return Promise.resolve(version)
form = require('resin-cli-form')
resin = require('resin-sdk-preconfigured')
resin.models.os.getSupportedVersions(deviceType)
.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
exports.versions =
signature: 'os versions <type>'
description: 'show the available resinOS versions for the given device type'
help: '''
Use this command to show the available resinOS versions for a certain device type.
Check available types with `resin devices supported`
Example:
$ resin os versions raspberrypi3
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
resin.models.os.getSupportedVersions(params.type)
.then ({ versions, recommended }) ->
versions.forEach (v) ->
console.log(formatVersion(v, v is recommended))
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.
Check available types with `resin devices supported`
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).
You can pass `--version menu` to pick the OS version from the interactive menu
of all available versions.
Examples:
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 1.24.1
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^1.20.0
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
$ resin os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
'''
permission: 'user'
options: [
{
signature: 'output'
description: 'output path'
parameter: 'output'
alias: 'o'
required: 'You have to specify the output location'
}
commandOptions.osVersion
]
action: (params, options, done) ->
Promise = require('bluebird')
unzip = require('unzip2')
fs = require('fs')
rindle = require('rindle')
manager = require('resin-image-manager')
visuals = require('resin-cli-visuals')
console.info("Getting device operating system for #{params.type}")
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) ->
bar = new visuals.Progress("Downloading Device OS#{displayVersion}")
spinner = new visuals.Spinner("Downloading Device OS#{displayVersion} (size unknown)")
stream.on 'progress', (state) ->
if state?
bar.update(state)
else
spinner.start()
stream.on 'end', ->
spinner.stop()
# 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)
return rindle.wait(stream.pipe(output)).return(options.output)
.tap (output) ->
console.info('The image was downloaded successfully')
.nodeify(done)
buildConfig = (image, deviceType, advanced = false) ->
form = require('resin-cli-form')
helpers = require('../utils/helpers')
helpers.getManifest(image, deviceType)
.get('options')
.then (questions) ->
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
return form.run(questions, { override })
exports.buildConfig =
signature: 'os build-config <image> <device-type>'
description: 'build the OS config and save it to the JSON file'
help: '''
Use this command to prebuild the OS config once and skip the interactive part of `resin os configure`.
Example:
$ resin os build-config ../path/rpi3.img raspberrypi3 --output rpi3-config.json
$ resin os configure ../path/rpi3.img 7cf02a6 --config "$(cat rpi3-config.json)"
'''
permission: 'user'
options: [
commandOptions.advancedConfig
{
signature: 'output'
description: 'the path to the output JSON file'
alias: 'o'
required: 'the output path is required'
parameter: 'output'
}
]
action: (params, options, done) ->
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))
.nodeify(done)
exports.configure =
signature: 'os configure <image> [uuid] [deviceApiKey]'
description: 'configure an os image'
help: '''
Use this command to configure a previously downloaded operating system image for
the specific device or for an application generally.
Note that device api keys are only supported on ResinOS 2.0.3+.
This comand still supports the *deprecated* format where the UUID and optionally device key
are passed directly on the command line, but the recommended way is to pass either an --app or
--device argument. The deprecated format will be remove in a future release.
Examples:
$ resin os configure ../path/rpi.img --device 7cf02a6
$ resin os configure ../path/rpi.img --device 7cf02a6 --deviceApiKey <existingDeviceKey>
$ resin os configure ../path/rpi.img --app MyApp
'''
permission: 'user'
options: [
commandOptions.advancedConfig
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
{
signature: 'config'
description: 'path to the config JSON file, see `resin os build-config`'
parameter: 'config'
}
]
action: (params, options, done) ->
fs = require('fs')
Promise = require('bluebird')
readFileAsync = Promise.promisify(fs.readFile)
resin = require('resin-sdk-preconfigured')
init = require('resin-device-init')
helpers = require('../utils/helpers')
patterns = require('../utils/patterns')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
if _.filter([
options.device
options.application
params.uuid
]).length != 1
patterns.expectedError '''
To configure an image, you must provide exactly one of:
* A device, with --device <uuid>
* An application, with --app <appname>
* [Deprecated] A device, passing its uuid directly on the command line
See the help page for examples:
$ resin help os configure
'''
if params.uuid
console.warn(
'Directly passing a UUID to `resin os configure` is deprecated. Pass it with --uuid <uuid> instead.' +
if params.deviceApiKey
' Device api keys can be passed with --deviceApiKey.\n'
else '\n'
)
uuid = options.device || params.uuid
deviceApiKey = options.deviceApiKey || params.deviceApiKey
console.info('Configuring operating system image')
configurationResourceType = if uuid then 'device' else 'application'
resin.models[configurationResourceType].get(uuid || options.application)
.then (appOrDevice) ->
Promise.try ->
if options.config
return readFileAsync(options.config, 'utf8')
.then(JSON.parse)
return buildConfig(params.image, appOrDevice.device_type, options.advanced)
.then (answers) ->
(if configurationResourceType == 'device'
generateDeviceConfig(appOrDevice, deviceApiKey, answers)
else
generateApplicationConfig(appOrDevice, answers)
).then (config) ->
init.configure(params.image, appOrDevice.device_type, config, answers)
.then(helpers.osProgressHandler)
.nodeify(done)
INIT_WARNING_MESSAGE = '''
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
'''
exports.initialize =
signature: 'os initialize <image>'
description: 'initialize an os image'
help: """
Use this command to initialize a device with previously configured operating system image.
#{INIT_WARNING_MESSAGE}
Examples:
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
"""
permission: 'user'
options: [
commandOptions.yes
{
signature: 'type'
description: 'device type (Check available types with `resin devices supported`)'
parameter: 'type'
alias: 't'
required: 'You have to specify a device type'
}
commandOptions.drive
]
action: (params, options, done) ->
Promise = require('bluebird')
umountAsync = Promise.promisify(require('umount').umount)
form = require('resin-cli-form')
patterns = require('../utils/patterns')
helpers = require('../utils/helpers')
console.info("""
Initializing device
#{INIT_WARNING_MESSAGE}
""")
helpers.getManifest(params.image, options.type)
.then (manifest) ->
return manifest.initialization?.options
.then (questions) ->
return form.run questions,
override:
drive: options.drive
.tap (answers) ->
return if not answers.drive?
patterns.confirm(
options.yes
"This will erase #{answers.drive}. Are you sure?"
"Going to erase #{answers.drive}."
)
.return(answers.drive)
.then(umountAsync)
.tap (answers) ->
return helpers.sudo([
'internal'
'osinit'
params.image
options.type
JSON.stringify(answers)
])
.then (answers) ->
return if not answers.drive?
# TODO: resin local makes use of ejectAsync, see below
# 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)
umountAsync(answers.drive).tap ->
console.info("You can safely remove #{answers.drive} now")
.nodeify(done)

View File

@ -1,94 +0,0 @@
_ = require('lodash')
visuals = require('resin-cli-visuals')
commandOptions = require('./command-options')
plugins = require('../plugins')
exports.list =
signature: 'plugins'
description: 'list all plugins'
help: '''
Use this command to list all the installed resin plugins.
Examples:
$ resin plugins
'''
permission: 'user'
action: (params, options, done) ->
plugins.list (error, resinPlugins) ->
return done(error) if error?
if _.isEmpty(resinPlugins)
console.log('You don\'t have any plugins yet')
return done()
console.log visuals.widgets.table.horizontal resinPlugins, [
'name'
'version'
'description'
'license'
]
return done()
exports.install =
signature: 'plugin install <name>'
description: 'install a plugin'
help: '''
Use this command to install a resin plugin
Use `--quiet` to prevent information logging.
Examples:
$ resin plugin install hello
'''
permission: 'user'
action: (params, options, done) ->
plugins.install params.name, (error) ->
return done(error) if error?
console.info("Plugin installed: #{params.name}")
return done()
exports.update =
signature: 'plugin update <name>'
description: 'update a plugin'
help: '''
Use this command to update a resin plugin
Use `--quiet` to prevent information logging.
Examples:
$ resin plugin update hello
'''
permission: 'user'
action: (params, options, done) ->
plugins.update params.name, (error, version) ->
return done(error) if error?
console.info("Plugin updated: #{params.name}@#{version}")
return done()
exports.remove =
signature: 'plugin rm <name>'
description: 'remove a plugin'
help: '''
Use this command to remove a resin.io plugin.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin plugin rm hello
$ resin plugin rm hello --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'plugin', options.yes, (callback) ->
plugins.remove(params.name, callback)
, (error) ->
return done(error) if error?
console.info("Plugin removed: #{params.name}")
return done()

View File

@ -1,21 +0,0 @@
open = require('open')
url = require('url')
settings = require('resin-settings-client')
exports.preferences =
signature: 'preferences'
description: 'open preferences form'
help: '''
Use this command to open the preferences form.
In the future, we will allow changing all preferences directly from the terminal.
For now, we open your default web browser and point it to the web based preferences form.
Examples:
$ resin preferences
'''
permission: 'user'
action: ->
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences')
open(absUrl)

276
lib/actions/preload.coffee Normal file
View File

@ -0,0 +1,276 @@
###
Copyright 2016-2017 Resin.io
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.
###
dockerUtils = require('../utils/docker')
LATEST = 'latest'
getApplicationsWithSuccessfulBuilds = (deviceType) ->
preload = require('resin-preload')
resin = require('resin-sdk-preconfigured')
resin.pine.get
resource: 'my_application'
options:
filter:
device_type: deviceType
build:
$any:
$alias: 'b'
$expr:
b:
status: 'success'
expand: preload.applicationExpandOptions
select: [ 'id', 'app_name', 'device_type', 'commit' ]
orderby: 'app_name asc'
selectApplication = (deviceType) ->
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
{ expectedError } = require('../utils/patterns')
applicationInfoSpinner = new visuals.Spinner('Downloading list of applications and builds.')
applicationInfoSpinner.start()
getApplicationsWithSuccessfulBuilds(deviceType)
.then (applications) ->
applicationInfoSpinner.stop()
if applications.length == 0
expectedError("You have no apps with successful builds for a '#{deviceType}' device type.")
form.ask
message: 'Select an application'
type: 'list'
choices: applications.map (app) ->
name: app.app_name
value: app
selectApplicationCommit = (builds) ->
form = require('resin-cli-form')
{ expectedError } = require('../utils/patterns')
if builds.length == 0
expectedError('This application has no successful builds.')
DEFAULT_CHOICE = {'name': LATEST, 'value': LATEST}
choices = [ DEFAULT_CHOICE ].concat builds.map (build) ->
name: "#{build.push_timestamp} - #{build.commit_hash}"
value: build.commit_hash
return form.ask
message: 'Select a build'
type: 'list'
default: LATEST
choices: choices
offerToDisableAutomaticUpdates = (application, commit) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
form = require('resin-cli-form')
if commit == LATEST or not application.should_track_latest_release
return Promise.resolve()
message = '''
This application is set to automatically update all devices to the latest available version.
This might be unexpected behaviour: with this enabled, the preloaded device will still
download and install the latest build once it is online.
Do you want to disable automatic updates for this application?
'''
form.ask
message: message,
type: 'confirm'
.then (update) ->
if not update
return
resin.pine.patch
resource: 'application'
id: application.id
body:
should_track_latest_release: false
module.exports =
signature: 'preload <image>'
description: '(beta) preload an app on a disk image (or Edison zip archive)'
help: '''
Warning: "resin preload" requires Docker to be correctly installed in
your shell environment. For more information (including Windows support)
please check the README here: https://github.com/resin-io/resin-cli .
Use this command to preload an application to a local disk image (or
Edison zip archive) with a built commit from Resin.io.
This can be used with cloud builds, or images deployed with resin deploy.
Examples:
$ resin preload resin.img --app 1234 --commit e1f2592fc6ee949e68756d4f4a48e49bff8d72a0 --splash-image some-image.png
$ resin preload resin.img
'''
permission: 'user'
primary: true
options: dockerUtils.appendConnectionOptions [
{
signature: 'app'
parameter: 'appId'
description: 'id of the application to preload'
alias: 'a'
}
{
signature: 'commit'
parameter: 'hash'
description: '''
a specific application commit to preload, use "latest" to specify the latest commit
(ignored if no appId is given)
'''
alias: 'c'
}
{
signature: 'splash-image'
parameter: 'splashImage.png'
description: 'path to a png image to replace the splash screen'
alias: 's'
}
{
signature: 'dont-check-device-type'
boolean: true
description: 'Disables check for matching device types in image and application'
}
]
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
streamToPromise = require('stream-to-promise')
form = require('resin-cli-form')
preload = require('resin-preload')
errors = require('resin-errors')
visuals = require('resin-cli-visuals')
nodeCleanup = require('node-cleanup')
{ expectedError } = require('../utils/patterns')
progressBars = {}
progressHandler = (event) ->
progressBar = progressBars[event.name]
if not progressBar
progressBar = progressBars[event.name] = new visuals.Progress(event.name)
progressBar.update(percentage: event.percentage)
spinners = {}
spinnerHandler = (event) ->
spinner = spinners[event.name]
if not spinner
spinner = spinners[event.name] = new visuals.Spinner(event.name)
if event.action == 'start'
spinner.start()
else
console.log()
spinner.stop()
options.image = params.image
options.appId = options.app
delete options.app
options.splashImage = options['splash-image']
delete options['splash-image']
if options['dont-check-device-type'] and not options.appId
expectedError('You need to specify an app id if you disable the device type check.')
# Get a configured dockerode instance
dockerUtils.getDocker(options)
.then (docker) ->
preloader = new preload.Preloader(
resin,
docker,
options.appId,
options.commit,
options.image,
options.splashImage,
options.proxy,
)
gotSignal = false
nodeCleanup (exitCode, signal) ->
if signal
gotSignal = true
nodeCleanup.uninstall() # don't call cleanup handler again
preloader.cleanup()
.then ->
# calling process.exit() won't inform parent process of signal
process.kill(process.pid, signal)
return false
if process.env.DEBUG
preloader.stderr.pipe(process.stderr)
preloader.on('progress', progressHandler)
preloader.on('spinner', spinnerHandler)
return new Promise (resolve, reject) ->
preloader.on('error', reject)
preloader.build()
.then ->
preloader.prepare()
.then ->
preloader.getDeviceTypeAndPreloadedBuilds()
.then (info) ->
Promise.try ->
if options.appId
return preloader.fetchApplication()
.catch(errors.ResinApplicationNotFound, expectedError)
selectApplication(info.device_type)
.then (application) ->
preloader.setApplication(application)
# Check that the app device type and the image device type match
if not options['dont-check-device-type'] and info.device_type != application.device_type
expectedError(
"Image device type (#{info.device_type}) and application device type (#{application.device_type}) do not match"
)
# Use the commit given as --commit or show an interactive commit selection menu
Promise.try ->
if options.commit
if options.commit == LATEST and application.commit
# handle `--commit latest`
return LATEST
else if not _.find(application.build, commit_hash: options.commit)
expectedError('There is no build matching this commit')
return options.commit
selectApplicationCommit(application.build)
.then (commit) ->
if commit == LATEST
preloader.commit = application.commit
else
preloader.commit = commit
# Propose to disable automatic app updates if the commit is not the latest
offerToDisableAutomaticUpdates(application, commit)
.then ->
builds = info.preloaded_builds.map (build) ->
build.slice(-preload.BUILD_HASH_LENGTH)
if preloader.commit in builds
throw new preload.errors.ResinError('This build is already preloaded in this image.')
# All options are ready: preload the image.
preloader.preload()
.catch(preload.errors.ResinError, expectedError)
.then(resolve)
.catch(reject)
.then(done)
.finally ->
if not gotSignal
preloader.cleanup()

View File

@ -0,0 +1,34 @@
###
Copyright 2016-2017 Resin.io
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.
###
exports.list =
signature: 'settings'
description: 'print current settings'
help: '''
Use this command to display detected settings
Examples:
$ resin settings
'''
action: (params, options, done) ->
resin = require('resin-sdk-preconfigured')
prettyjson = require('prettyjson')
resin.settings.getAll()
.then(prettyjson.render)
.then(console.log)
.nodeify(done)

130
lib/actions/ssh.coffee Normal file
View File

@ -0,0 +1,130 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports =
signature: 'ssh [uuid]'
description: '(beta) get a shell into the running app container of a device'
help: '''
Warning: 'resin ssh' requires an openssh-compatible client to be correctly
installed in your shell environment. For more information (including Windows
support) please check the README here: https://github.com/resin-io/resin-cli
Use this command to get a shell into the running application container of
your device.
Examples:
$ resin ssh MyApp
$ resin ssh 7cf02a6
$ resin ssh 7cf02a6 --port 8080
$ resin ssh 7cf02a6 -v
'''
permission: 'user'
primary: true
options: [
signature: 'port'
parameter: 'port'
description: 'ssh gateway port'
alias: 'p'
,
signature: 'verbose'
boolean: true
description: 'increase verbosity'
alias: 'v'
,
signature: 'noproxy'
boolean: true
description: "don't use the proxy configuration for this connection.
Only makes sense if you've configured proxy globally."
]
action: (params, options, done) ->
child_process = require('child_process')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
bash = require('bash')
hasbin = require('hasbin')
{ getSubShellCommand } = require('../utils/helpers')
patterns = require('../utils/patterns')
options.port ?= 22
verbose = if options.verbose then '-vvv' else ''
proxyConfig = global.PROXY_CONFIG
useProxy = !!proxyConfig and not options.noproxy
getSshProxyCommand = (hasTunnelBin) ->
return '' if not useProxy
if not hasTunnelBin
console.warn('''
Proxy is enabled but the `proxytunnel` binary cannot be found.
Please install it if you want to route the `resin ssh` requests through the proxy.
Alternatively you can pass `--noproxy` param to the `resin ssh` command to ignore the proxy config
for the `ssh` requests.
Attemmpting the unproxied request for now.
''')
return ''
tunnelOptions =
proxy: "#{proxyConfig.host}:#{proxyConfig.port}"
dest: '%h:%p'
{ proxyAuth } = proxyConfig
if proxyAuth
i = proxyAuth.indexOf(':')
_.assign tunnelOptions,
user: proxyAuth.substring(0, i)
pass: proxyAuth.substring(i + 1)
proxyCommand = "proxytunnel #{bash.args(tunnelOptions, '--', '=')}"
return "-o #{bash.args({ ProxyCommand: proxyCommand }, '', '=')}"
Promise.try ->
return false if not params.uuid
return resin.models.device.has(params.uuid)
.then (uuidExists) ->
return params.uuid if uuidExists
return patterns.inferOrSelectDevice()
.then (uuid) ->
console.info("Connecting to: #{uuid}")
resin.models.device.get(uuid)
.then (device) ->
throw new Error('Device is not online') if not device.is_online
Promise.props
username: resin.auth.whoami()
uuid: device.uuid
# get full uuid
containerId: resin.models.device.getApplicationInfo(device.uuid).get('containerId')
proxyUrl: resin.settings.get('proxyUrl')
hasTunnelBin: if useProxy then hasbin('proxytunnel') else null
.then ({ username, uuid, containerId, proxyUrl, hasTunnelBin }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
sshProxyCommand = getSshProxyCommand(hasTunnelBin)
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
#{sshProxyCommand} \
-p #{options.port} #{username}@ssh.#{proxyUrl} enter #{uuid} #{containerId}"
subShellCommand = getSubShellCommand(command)
child_process.spawn subShellCommand.program, subShellCommand.args,
stdio: 'inherit'
.nodeify(done)

17
lib/actions/sync.coffee Normal file
View File

@ -0,0 +1,17 @@
###
Copyright 2016-2017 Resin.io
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.
###
module.exports = require('resin-sync').capitano('resin-cli')

View File

@ -1,26 +0,0 @@
selfupdate = require('selfupdate')
packageJSON = require('../../package.json')
exports.update =
signature: 'update'
description: 'update the resin cli'
help: '''
Use this command to update the Resin CLI
This command outputs information about the update process.
Use `--quiet` to remove that output.
The Resin CLI checks for updates once per day.
Major updates require a manual update with this update command,
while minor updates are applied automatically.
Examples:
$ resin update
'''
action: (params, options, done) ->
selfupdate.update packageJSON, (error, version) ->
return done(error) if error?
console.info("Updated #{packageJSON.name} to version #{version}.")
return done()

56
lib/actions/util.coffee Normal file
View File

@ -0,0 +1,56 @@
###
Copyright 2016-2017 Resin.io
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.
###
_ = require('lodash')
exports.availableDrives =
# TODO: dedupe with https://github.com/resin-io-modules/resin-cli-visuals/blob/master/lib/widgets/drive/index.coffee
signature: 'util available-drives'
description: 'list available drives'
help: """
Use this command to list your machine's drives usable for writing the OS image to.
Skips the system drives.
"""
action: ->
Promise = require('bluebird')
drivelist = require('drivelist')
driveListAsync = Promise.promisify(drivelist.list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
formatDrive = (drive) ->
size = drive.size / 1000000000
return {
device: drive.device
size: "#{size.toFixed(1)} GB"
description: drive.description
}
getDrives = ->
driveListAsync().then (drives) ->
return _.reject(drives, system: true)
getDrives()
.then (drives) ->
if not drives.length
console.error("#{chalk.red('x')} No available drives were detected, plug one in!")
return
console.log visuals.table.horizontal drives.map(formatDrive), [
'device'
'size'
'description'
]

75
lib/actions/wizard.coffee Normal file
View File

@ -0,0 +1,75 @@
###
Copyright 2016-2017 Resin.io
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.
###
exports.wizard =
signature: 'quickstart [name]'
description: 'getting started with resin.io'
help: '''
Use this command to run a friendly wizard to get started with resin.io.
The wizard will guide you through:
- Create an application.
- Initialise an SDCard with the resin.io operating system.
- Associate an existing project directory with your resin.io application.
- Push your project to your devices.
Examples:
$ resin quickstart
$ resin quickstart MyApp
'''
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
capitanoRunAsync = Promise.promisify(require('capitano').run)
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
resin.auth.isLoggedIn().then (isLoggedIn) ->
return if isLoggedIn
console.info('Looks like you\'re not logged in yet!')
console.info('Lets go through a quick wizard to get you started.\n')
return capitanoRunAsync('login')
.then ->
return if params.name?
patterns.selectOrCreateApplication().tap (applicationName) ->
resin.models.application.has(applicationName).then (hasApplication) ->
return applicationName if hasApplication
capitanoRunAsync("app create #{applicationName}")
.then (applicationName) ->
params.name = applicationName
.then ->
return capitanoRunAsync("device init --application #{params.name}")
.tap(patterns.awaitDevice)
.then (uuid) ->
return capitanoRunAsync("device #{uuid}")
.then ->
return resin.models.application.get(params.name)
.then (application) ->
console.log """
Your device is ready to start pushing some code!
Check our official documentation for more information:
http://docs.resin.io/#/pages/introduction/introduction.md
Clone an example or go to an existing application directory and run:
$ git remote add resin #{application.git_repository}
$ git push resin master
"""
.nodeify(done)

View File

@ -1,49 +1,110 @@
###
Copyright 2016-2017 Resin.io
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.
###
Raven = require('raven')
Raven.disableConsoleAlerts()
Raven.config require('./config').sentryDsn,
captureUnhandledRejections: true
release: require('../package.json').version
.install (logged, error) ->
console.error(error)
process.exit(1)
Raven.setContext
extra:
args: process.argv
node_version: process.version
validNodeVersions = require('../package.json').engines.node
if not require('semver').satisfies(process.version, validNodeVersions)
console.warn """
Warning: this version of Node does not match the requirements of this package.
This package expects #{validNodeVersions}, but you're using #{process.version}.
This may cause unexpected behaviour.
To upgrade your Node, visit https://nodejs.org/en/download/
"""
# Doing this before requiring any other modules,
# including the 'resin-sdk', to prevent any module from reading the http proxy config
# before us
globalTunnel = require('global-tunnel-ng')
settings = require('resin-settings-client')
try
proxy = settings.get('proxy') or null
catch
proxy = null
# Init the tunnel even if the proxy is not configured
# because it can also get the proxy from the http(s)_proxy env var
# If that is not set as well the initialize will do nothing
globalTunnel.initialize(proxy)
# TODO: make this a feature of capitano https://github.com/resin-io/capitano/issues/48
global.PROXY_CONFIG = globalTunnel.proxyConfig
_ = require('lodash')
async = require('async')
Promise = require('bluebird')
capitano = require('capitano')
resin = require('resin-sdk')
capitanoExecuteAsync = Promise.promisify(capitano.execute)
# We don't yet use resin-sdk directly everywhere, but we set up shared
# options correctly so we can do safely in submodules
require('resin-sdk').setSharedOptions(
apiUrl: settings.get('apiUrl')
imageMakerUrl: settings.get('imageMakerUrl')
dataDirectory: settings.get('dataDirectory')
retries: 2
)
# Keep using sdk-preconfigured for now, but only temporarily
resin = require('resin-sdk-preconfigured')
actions = require('./actions')
errors = require('./errors')
plugins = require('./plugins')
update = require('./update')
events = require('./events')
plugins = require('./utils/plugins')
update = require('./utils/update')
# Assign bluebird as the global promise library
# stream-to-promise will produce native promises if not
# for this module, which could wreak havoc in this
# bluebird-only codebase.
require('any-promise/register/bluebird')
capitano.permission 'user', (done) ->
resin.auth.isLoggedIn (isLoggedIn) ->
resin.auth.isLoggedIn().then (isLoggedIn) ->
if not isLoggedIn
return done(new Error('You have to log in'))
return done()
throw new Error '''
You have to log in to continue
Run the following command to go through the login wizard:
$ resin login
'''
.nodeify(done)
capitano.command
signature: '*'
action: ->
capitano.execute(command: 'help')
# ---------- Options ----------
capitano.globalOption
signature: 'quiet'
description: 'quiet (no output)'
boolean: true
alias: 'q'
capitano.globalOption
signature: 'project'
parameter: 'path'
description: 'project path'
alias: 'j'
capitano.globalOption
signature: 'version'
description: actions.info.version.description
boolean: true
alias: 'v'
# We don't do anything in response to this options
# explicitly. We use InquirerJS to provide CLI widgets,
# and that module understands --no-color automatically.
capitano.globalOption
signature: 'no-color'
description: 'disable colour highlighting'
signature: 'help'
boolean: true
alias: 'h'
# ---------- Info Module ----------
capitano.command(actions.info.version)
@ -51,6 +112,9 @@ capitano.command(actions.info.version)
# ---------- Help Module ----------
capitano.command(actions.help.help)
# ---------- Wizard Module ----------
capitano.command(actions.wizard.wizard)
# ---------- Auth Module ----------
capitano.command(actions.auth.login)
capitano.command(actions.auth.logout)
@ -62,8 +126,6 @@ capitano.command(actions.app.create)
capitano.command(actions.app.list)
capitano.command(actions.app.remove)
capitano.command(actions.app.restart)
capitano.command(actions.app.associate)
capitano.command(actions.app.init)
capitano.command(actions.app.info)
# ---------- Device Module ----------
@ -71,20 +133,21 @@ capitano.command(actions.device.list)
capitano.command(actions.device.supported)
capitano.command(actions.device.rename)
capitano.command(actions.device.init)
capitano.command(actions.device.await)
capitano.command(actions.device.info)
capitano.command(actions.device.remove)
capitano.command(actions.device.identify)
# ---------- Drive Module ----------
capitano.command(actions.drive.list)
capitano.command(actions.device.reboot)
capitano.command(actions.device.shutdown)
capitano.command(actions.device.enableDeviceUrl)
capitano.command(actions.device.disableDeviceUrl)
capitano.command(actions.device.getDeviceUrl)
capitano.command(actions.device.hasDeviceUrl)
capitano.command(actions.device.register)
capitano.command(actions.device.move)
capitano.command(actions.device.info)
# ---------- Notes Module ----------
capitano.command(actions.notes.set)
# ---------- Preferences Module ----------
capitano.command(actions.preferences.preferences)
# ---------- Keys Module ----------
capitano.command(actions.keys.list)
capitano.command(actions.keys.add)
@ -97,49 +160,65 @@ capitano.command(actions.env.add)
capitano.command(actions.env.rename)
capitano.command(actions.env.remove)
# ---------- OS Module ----------
capitano.command(actions.os.versions)
capitano.command(actions.os.download)
capitano.command(actions.os.buildConfig)
capitano.command(actions.os.configure)
capitano.command(actions.os.initialize)
# ---------- Config Module ----------
capitano.command(actions.config.read)
capitano.command(actions.config.write)
capitano.command(actions.config.inject)
capitano.command(actions.config.reconfigure)
capitano.command(actions.config.generate)
# ---------- Settings Module ----------
capitano.command(actions.settings.list)
# ---------- Logs Module ----------
capitano.command(actions.logs)
# ---------- Examples Module ----------
capitano.command(actions.examples.list)
capitano.command(actions.examples.clone)
capitano.command(actions.examples.info)
# ---------- Sync Module ----------
capitano.command(actions.sync)
# ---------- Plugins Module ----------
capitano.command(actions.plugin.list)
capitano.command(actions.plugin.install)
capitano.command(actions.plugin.update)
capitano.command(actions.plugin.remove)
# ---------- Preload Module ----------
capitano.command(actions.preload)
# ---------- Update Module ----------
capitano.command(actions.update.update)
# ---------- SSH Module ----------
capitano.command(actions.ssh)
changeProjectDirectory = (directory) ->
try
process.chdir(directory)
catch
errors.handle(new Error("Invalid project: #{directory}"))
# ---------- Local ResinOS Module ----------
capitano.command(actions.local.configure)
capitano.command(actions.local.flash)
capitano.command(actions.local.logs)
capitano.command(actions.local.push)
capitano.command(actions.local.ssh)
capitano.command(actions.local.scan)
capitano.command(actions.local.stop)
async.waterfall([
# ---------- Public utils ----------
capitano.command(actions.util.availableDrives)
(callback) ->
update.check(callback)
# ---------- Internal utils ----------
capitano.command(actions.internal.osInit)
(callback) ->
plugins.register('resin-plugin-', callback)
#------------ Local build and deploy -------
capitano.command(actions.build)
capitano.command(actions.deploy)
(callback) ->
cli = capitano.parse(process.argv)
update.notify()
if cli.global.quiet or not process.stdout.isTTY
console.info = _.noop
plugins.register(/^resin-plugin-(.+)$/).then ->
cli = capitano.parse(process.argv)
if cli.global.project?
changeProjectDirectory(cli.global.project)
if cli.global.version
actions.info.version.action(null, null, callback)
runCommand = ->
if cli.global?.help
capitanoExecuteAsync(command: "help #{cli.command ? ''}")
else
capitano.execute(cli, callback)
capitanoExecuteAsync(cli)
], errors.handle)
Promise.all([events.trackCommand(cli), runCommand()])
.catch(errors.handle)

63
lib/auth/index.coffee Normal file
View File

@ -0,0 +1,63 @@
###
Copyright 2016 Resin.io
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.
###
###*
# @module auth
###
open = require('opn')
resin = require('resin-sdk-preconfigured')
server = require('./server')
utils = require('./utils')
###*
# @summary Login to the Resin CLI using the web dashboard
# @function
# @public
#
# @description
# This function opens the user's default browser and points it
# to the Resin.io dashboard where the session token exchange will
# take place.
#
# Once the the token is retrieved, it's automatically persisted.
#
# @fulfil {String} - session token
# @returns {Promise}
#
# @example
# auth.login().then (sessionToken) ->
# console.log('I\'m logged in!')
# console.log("My session token is: #{sessionToken}")
###
exports.login = ->
options =
port: 8989
path: '/auth'
# Needs to be 127.0.0.1 not localhost, because the ip only is whitelisted
# from mixed content warnings (as the target of a form in the result page)
callbackUrl = "http://127.0.0.1:#{options.port}#{options.path}"
return utils.getDashboardLoginURL(callbackUrl).then (loginUrl) ->
# Leave a bit of time for the
# server to get up and runing
setTimeout ->
open(loginUrl)
, 1000
return server.awaitForToken(options)
.tap(resin.auth.loginWithToken)

21
lib/auth/pages/error.ejs Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Error</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
</head>
<body>
<div class="center">
<img class="icon" src="./static/images/sad.png" inline>
<h1>Something went wrong</h1>
<p>You couldn't login to the Resin CLI for some reason</p>
<br>
<br>
<a href="https://forums.resin.io/" class="button danger">Get help in our forums</a>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,60 @@
html,
body {
height: 100%;
}
body {
text-align: center;
background-color: #fff;
color: rgb(24, 24, 24);
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
position: relative;
}
.center {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 50%;
height: 50%;
}
.icon {
display: block;
width: 40px;
height: 45px;
margin: 0 auto;
margin-bottom: 15px;
}
h1 {
font-size: 3rem;
margin: 0;
margin-bottom: 12px;
}
p {
color: rgb(99, 99, 99);
font-size: 1.1rem;
margin: 0;
margin-bottom: 15px;
}
a.button {
padding: 15px 25px;
border-radius: 5px;
text-decoration: none;
}
a.button.danger {
background-color: rgb(235, 110, 111);
color: #fff;
}
a.button.normal {
background-color: rgb(252, 191, 44);
color: #fff;
}

View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Resin CLI - Success</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
</head>
<body>
<div class="center">
<img class="icon" src="./static/images/happy.png" inline>
<h1>Success!</h1>
<p>You successfully logged in the Resin CLI</p>
<br>
<br>
<a href="<%= dashboardUrl %>" class="button normal">Go to the dashboard</a>
</div>
</body>
</html>

101
lib/auth/server.coffee Normal file
View File

@ -0,0 +1,101 @@
###
Copyright 2016 Resin.io
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.
###
express = require('express')
path = require('path')
bodyParser = require('body-parser')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
utils = require('./utils')
createServer = ({ port, isDev } = {}) ->
app = express()
app.use bodyParser.urlencoded
extended: true
app.set('view engine', 'ejs')
app.set('views', path.join(__dirname, 'pages'))
if isDev
app.use(express.static(path.join(__dirname, 'pages', 'static')))
server = app.listen(port)
return { app, server }
###*
# @summary Await for token
# @function
# @protected
#
# @param {Object} options - options
# @param {String} options.path - callback path
# @param {Number} options.port - http port
#
# @example
# server.awaitForToken
# path: '/auth'
# port: 9001
# .then (token) ->
# console.log(token)
###
exports.awaitForToken = (options) ->
{ app, server } = createServer(port: options.port)
return new Promise (resolve, reject) ->
closeServer = (errorMessage, successPayload) ->
server.close ->
if errorMessage
reject(new Error(errorMessage))
return
resolve(successPayload)
renderAndDone = ({ request, response, viewName, errorMessage, statusCode, token }) ->
return getContext(viewName)
.then (context) ->
response.status(statusCode || 200).render(viewName, context)
request.connection.destroy()
closeServer(errorMessage, token)
app.post options.path, (request, response) ->
token = request.body.token?.trim()
Promise.try ->
if not token
throw new Error('No token')
return utils.isTokenValid(token)
.tap (isValid) ->
if not isValid
throw new Error('Invalid token')
.then ->
renderAndDone({ request, response, viewName: 'success', token })
.catch (error) ->
renderAndDone({
request, response, viewName: 'error',
statusCode: 401, errorMessage: error.message
})
app.use (request, response) ->
response.status(404).send('Not found')
closeServer('Unknown path or verb')
exports.getContext = getContext = (viewName) ->
if viewName is 'success'
return Promise.props
dashboardUrl: resin.settings.get('dashboardUrl')
return Promise.resolve({})

75
lib/auth/utils.coffee Normal file
View File

@ -0,0 +1,75 @@
###
Copyright 2016 Resin.io
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.
###
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
url = require('url')
Promise = require('bluebird')
###*
# @summary Get dashboard CLI login URL
# @function
# @protected
#
# @param {String} callbackUrl - callback url
# @fulfil {String} - dashboard login url
# @returns {Promise}
#
# @example
# utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
# console.log(url)
###
exports.getDashboardLoginURL = (callbackUrl) ->
# Encode percentages signs from the escaped url
# characters to avoid angular getting confused.
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25')
resin.settings.get('dashboardUrl').then (dashboardUrl) ->
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
###*
# @summary Check if a token is valid
# @function
# @protected
#
# @description
# This function checks that the token is not only well-structured
# but that it also authenticates with the server successfully.
#
# @param {String} sessionToken - token
# @fulfil {Boolean} - whether is valid or not
# @returns {Promise}
#
# utils.isTokenValid('...').then (isValid) ->
# if isValid
# console.log('Token is valid!')
###
exports.isTokenValid = (sessionToken) ->
if not sessionToken? or _.isEmpty(sessionToken.trim())
return Promise.resolve(false)
return resin.token.get().then (currentToken) ->
resin.auth.loginWithToken(sessionToken)
.return(sessionToken)
.then(resin.auth.isLoggedIn)
.tap (isLoggedIn) ->
return if isLoggedIn
if currentToken?
return resin.auth.loginWithToken(currentToken)
else
return resin.auth.logout()

1
lib/config.ts Normal file
View File

@ -0,0 +1 @@
exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239'

View File

@ -1,96 +0,0 @@
[
{
"name": "basic-resin-node-project",
"display_name": "Node.js Starter Project",
"repository": "https://github.com/resin-io/basic-resin-node-project",
"description": "This is a simple Hello, World project for node.js designed to act as a basis for future work. It demonstrates how to install native Linux packages and configure your application."
},
{
"name": "cimon",
"display_name": "Cimon",
"repository": "https://bitbucket.org/efwe/cimon",
"description": "A simple tool for reading temperatures from a USB-enabled thermometer. This project is used as the backend to efwe's awesome temperature visualisation at 123k.de.",
"author": "efwe"
},
{
"name": "firebaseDTL",
"display_name": "Digital Temperature Logger",
"repository": "https://github.com/shaunmulligan/firebaseDTL",
"description": "A Firebase-backed Digital Temperature Logger allowing you to connect devices with multiple temperature sensors to a central cloud-based datastore.",
"author": "Shaun Mulligan"
},
{
"name": "digitiser",
"display_name": "Digitiser",
"repository": "https://github.com/shaunmulligan/digitiser",
"description": "A tool for displaying integer values from a JSON endpoint on a MAX7219 7-segment display.",
"author": "Shaun Mulligan"
},
{
"name": "basic-gpio",
"display_name": "Example Pi Pins Application",
"repository": "https://github.com/shaunmulligan/basic-gpio",
"description": "A simple application which demonstrates the use of the Pi Pins library to interface with GPIO.",
"author": "Shaun Mulligan"
},
{
"name": "coder",
"display_name": "Google Coder",
"repository": "https://github.com/resin-io/coder",
"description": "Resin.io-enabled version of Google's excellent Coder project which makes it easy to develop web projects on your device."
},
{
"name": "hoversnap",
"display_name": "Hoversnap",
"repository": "https://github.com/resin-io/hoversnap",
"description": "A tool for controlling a camera using a foot switch in order to capture shots in which people appear to be flying."
},
{
"name": "resin-piminer",
"display_name": "Pi Miner",
"repository": "https://github.com/csquared/resin-piminer",
"description": "A bitcoin miner for the Raspberry Pi.",
"author": "Chris Continanza"
},
{
"name": "resin-cctv",
"display_name": "Resin CCTV",
"repository": "https://github.com/abresas/resin-cctv",
"description": "A project which allows you to use your devices as a CCTV camera system which hooks into Dropbox.",
"author": "Aleksis Brezas"
},
{
"name": "resin_player",
"display_name": "Resin Player",
"repository": "https://bitbucket.org/lifeeth/resin_player/",
"description": "A project which allows you to play squeezebox media through your devices.",
"author": "Praneeth Bodduluri"
},
{
"name": "salesforceTemp",
"display_name": "Salesforce Temperature Probe",
"repository": "https://github.com/shaunmulligan/salesforceTemp",
"description": "Example application for interfacing with a temperature probe using Salesforce.com.",
"author": "Shaun Mulligan"
},
{
"name": "sms2speech",
"display_name": "SMS to Speech",
"repository": "https://github.com/alexandrosm/sms2speech",
"description": "A simple tool which uses Twillio to read out incoming SMS messages.",
"author": "Alexandros Marinos"
},
{
"name": "resin-kiosk",
"display_name": "Simple Digitiser Kiosk",
"repository": "https://bitbucket.org/lifeeth/resin-kiosk",
"description": "Displays values from a JSON endpoint on your browser in kiosk mode",
"author": "Praneeth Bodduluri"
},
{
"name": "text2speech",
"display_name": "Text to Speech Converter",
"repository": "https://github.com/resin-io/text2speech",
"description": "A simple application that makes your device speak out loud."
}
]

View File

@ -1,16 +0,0 @@
_ = require('lodash')
os = require('os')
path = require('path')
isWindows = ->
return os.platform() is 'win32'
exports.shouldElevate = (error) ->
return _.all [
isWindows()
error.code is 'EPERM' or error.code is 'EACCES'
]
exports.run = (command) ->
return if not isWindows()
require('windosu').exec(command)

View File

@ -1,40 +1,38 @@
_ = require('lodash')
os = require('os')
###
Copyright 2016-2017 Resin.io
exports.handle = (error, exit = true) ->
return if not error? or error not instanceof Error
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.
###
chalk = require('chalk')
errors = require('resin-cli-errors')
patterns = require('./utils/patterns')
Raven = require('raven')
Promise = require('bluebird')
captureException = Promise.promisify(Raven.captureException.bind(Raven))
exports.handle = (error) ->
message = errors.interpret(error)
return if not message?
if process.env.DEBUG
console.error(error.stack)
else
if error.code is 'EISDIR'
console.error("File is a directory: #{error.path}")
message = error.stack
else if error.code is 'ENOENT'
console.error("No such file or directory: #{error.path}")
patterns.printErrorMessage(message)
else if error.code is 'EACCES' or error.code is 'EPERM'
message = 'You don\'t have enough privileges to run this operation.\n'
if os.platform() is 'win32'
message += 'Run a new Command Prompt as administrator and try running this command again.'
else
message += 'Try running this command again prefixing it with `sudo`.'
console.error(message)
else if error.code is 'ENOGIT'
console.error '''
Git is not installed on this system.
Head over to http://git-scm.com to install it and run this command again.
'''
else if error.message?
console.error(error.message)
if _.isNumber(error.exitCode)
errorCode = error.exitCode
else
errorCode = 1
process.exit(errorCode) if exit
captureException(error)
.timeout(1000)
.catch(-> # Ignore any errors (from error logging, or timeouts)
).finally ->
process.exit(error.exitCode or 1)

34
lib/events.coffee Normal file
View File

@ -0,0 +1,34 @@
_ = require('lodash')
Mixpanel = require('mixpanel')
Raven = require('raven')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
packageJSON = require('../package.json')
exports.getLoggerInstance = _.memoize ->
return resin.models.config.getMixpanelToken().then(Mixpanel.init)
exports.trackCommand = (capitanoCommand) ->
capitanoStateGetMatchCommandAsync = Promise.promisify(require('capitano').state.getMatchCommand)
return Promise.props
resinUrl: resin.settings.get('resinUrl')
username: resin.auth.whoami().catchReturn(undefined)
mixpanel: exports.getLoggerInstance()
.then ({ username, resinUrl, mixpanel }) ->
return capitanoStateGetMatchCommandAsync(capitanoCommand.command).then (command) ->
Raven.mergeContext(user: {
id: username,
username
})
mixpanel.track "[CLI] #{command.signature.toString()}",
distinct_id: username
argv: process.argv.join(' ')
version: packageJSON.version
node: process.version
arch: process.arch
resinUrl: resinUrl
platform: process.platform
command: capitanoCommand
.timeout(100)
.catchReturn()

View File

@ -1,34 +0,0 @@
Nplugm = require('nplugm')
_ = require('lodash')
capitano = require('capitano')
nplugm = null
registerPlugin = (plugin) ->
return capitano.command(plugin) if not _.isArray(plugin)
return _.each(plugin, capitano.command)
exports.register = (prefix, callback) ->
nplugm = new Nplugm(prefix)
nplugm.list (error, plugins) ->
return callback(error) if error?
for plugin in plugins
try
registerPlugin(nplugm.require(plugin))
catch error
console.error(error.message)
return callback()
exports.list = ->
nplugm.list.apply(nplugm, arguments)
exports.install = ->
nplugm.install.apply(nplugm, arguments)
exports.update = ->
nplugm.update.apply(nplugm, arguments)
exports.remove = ->
nplugm.remove.apply(nplugm, arguments)

View File

@ -1,26 +0,0 @@
updateNotifier = require('update-notifier')
packageJSON = require('../package.json')
updateAction = require('./actions/update')
exports.perform = (callback) ->
updateAction.update.action(null, null, callback)
exports.notify = (update) ->
return if not process.stdout.isTTY
console.log """
> Major update available: #{update.current} -> #{update.latest}
> Run resin update to update.
> Beware that a major release might introduce breaking changes.\n
"""
exports.check = (callback) ->
notifier = updateNotifier(pkg: packageJSON)
return callback() if not notifier.update?
if notifier.update.type is 'major'
exports.notify(notifier.update)
return callback()
console.log("Performing #{notifier.update.type} update, hold tight...")
exports.perform(callback)

97
lib/utils/config.coffee Normal file
View File

@ -0,0 +1,97 @@
###
Copyright 2016-2017 Resin.io
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.
###
exports.generateBaseConfig = (application, options) ->
Promise = require('bluebird')
_ = require('lodash')
deviceConfig = require('resin-device-config')
resin = require('resin-sdk-preconfigured')
options = _.mapValues options, (value, key) ->
if key == 'appUpdatePollInterval'
value * 60 * 1000
else
value
Promise.props
userId: resin.auth.getUserId()
username: resin.auth.whoami()
apiUrl: resin.settings.get('apiUrl')
vpnUrl: resin.settings.get('vpnUrl')
registryUrl: resin.settings.get('registryUrl')
deltaUrl: resin.settings.get('deltaUrl')
pubNubKeys: resin.models.config.getPubNubKeys()
mixpanelToken: resin.models.config.getMixpanelToken()
.then (results) ->
deviceConfig.generate
application: application
user:
id: results.userId
username: results.username
endpoints:
api: results.apiUrl
vpn: results.vpnUrl
registry: results.registryUrl
delta: results.deltaUrl
pubnub: results.pubNubKeys
mixpanel:
token: results.mixpanelToken
, options
exports.generateApplicationConfig = (application, options) ->
exports.generateBaseConfig(application, options)
.tap (config) ->
authenticateWithApplicationKey(config, application.id)
exports.generateDeviceConfig = (device, deviceApiKey, options) ->
resin = require('resin-sdk-preconfigured')
resin.models.application.get(device.application_name)
.then (application) ->
exports.generateBaseConfig(application, options)
.tap (config) ->
# Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain
# the expected version for this config and generate one when we know it's safe,
# but instead for now we fall back to app keys unless the user has explicitly opted in.
if deviceApiKey?
authenticateWithDeviceKey(config, device.uuid, deviceApiKey)
else
authenticateWithApplicationKey(config, application.id)
.then (config) ->
# Associate a device, to prevent the supervisor
# from creating another one on its own.
config.registered_at = Math.floor(Date.now() / 1000)
config.deviceId = device.id
config.uuid = device.uuid
return config
authenticateWithApplicationKey = (config, applicationNameOrId) ->
resin = require('resin-sdk-preconfigured')
resin.models.application.generateApiKey(applicationNameOrId)
.then (apiKey) ->
config.apiKey = apiKey
return config
authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) ->
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
Promise.try ->
customDeviceApiKey || resin.models.device.generateDeviceKey(uuid)
.then (deviceApiKey) ->
config.deviceApiKey = deviceApiKey
return config

420
lib/utils/docker.coffee Normal file
View File

@ -0,0 +1,420 @@
# Functions to help actions which rely on using docker
QEMU_VERSION = 'v2.5.50-resin-execve'
QEMU_BIN_NAME = 'qemu-execve'
# Use this function to seed an action's list of capitano options
# with the docker options. Using this interface means that
# all functions using docker will expose the same interface
#
# NOTE: Care MUST be taken when using the function, so as to
# not redefine/override options already provided.
exports.appendConnectionOptions = appendConnectionOptions = (opts) ->
opts.concat [
{
signature: 'docker'
parameter: 'docker'
description: 'Path to a local docker socket'
alias: 'P'
},
{
signature: 'dockerHost'
parameter: 'dockerHost'
description: 'The address of the host containing the docker daemon'
alias: 'h'
},
{
signature: 'dockerPort'
parameter: 'dockerPort'
description: 'The port on which the host docker daemon is listening'
alias: 'p'
},
{
signature: 'ca'
parameter: 'ca'
description: 'Docker host TLS certificate authority file'
},
{
signature: 'cert'
parameter: 'cert'
description: 'Docker host TLS certificate file'
},
{
signature: 'key'
parameter: 'key'
description: 'Docker host TLS key file'
},
]
# Use this function to seed an action's list of capitano options
# with the docker options. Using this interface means that
# all functions using docker will expose the same interface
#
# NOTE: Care MUST be taken when using the function, so as to
# not redefine/override options already provided.
exports.appendOptions = (opts) ->
appendConnectionOptions(opts).concat [
{
signature: 'tag'
parameter: 'tag'
description: 'The alias to the generated image'
alias: 't'
},
{
signature: 'buildArg'
parameter: 'arg'
description: 'Set a build-time variable (eg. "-B \'ARG=value\'"). Can be specified multiple times.'
alias: 'B'
},
{
signature: 'nocache'
description: "Don't use docker layer caching when building"
boolean: true
},
{
signature: 'emulated'
description: 'Run an emulated build using Qemu'
boolean: true
alias: 'e'
},
{
signature: 'squash'
description: 'Squash newly built layers into a single new layer'
boolean: true
}
]
exports.generateConnectOpts = generateConnectOpts = (opts) ->
Promise = require('bluebird')
buildDockerodeOpts = require('dockerode-options')
fs = require('mz/fs')
_ = require('lodash')
Promise.try ->
connectOpts = {}
# Firsly need to decide between a local docker socket
# and a host available over a host:port combo
if opts.docker? and not opts.dockerHost?
# good, local docker socket
connectOpts.socketPath = opts.docker
else if opts.dockerHost? and not opts.docker?
# Good a host is provided, and local socket isn't
connectOpts.host = opts.dockerHost
connectOpts.port = opts.dockerPort || 2376
else if opts.docker? and opts.dockerHost?
# Both provided, no obvious way to continue
throw new Error("Both a local docker socket and docker host have been provided. Don't know how to continue.")
else if process.env.DOCKER_HOST
# If no explicit options are provided, use the env
connectOpts = buildDockerodeOpts(process.env.DOCKER_HOST)
else
# No options anywhere, assume default docker local socket
connectOpts.socketPath = '/var/run/docker.sock'
# Now need to check if the user wants to connect over TLS
# to the host
# If any are set...
if (opts.ca? or opts.cert? or opts.key?)
# but not all
if not (opts.ca? and opts.cert? and opts.key?)
throw new Error('You must provide a CA, certificate and key in order to use TLS')
certBodies = {
ca: fs.readFile(opts.ca, 'utf-8')
cert: fs.readFile(opts.cert, 'utf-8')
key: fs.readFile(opts.key, 'utf-8')
}
return Promise.props(certBodies)
.then (toMerge) ->
_.merge(connectOpts, toMerge)
return connectOpts
exports.tarDirectory = tarDirectory = (dir) ->
Promise = require('bluebird')
tar = require('tar-stream')
klaw = require('klaw')
path = require('path')
fs = require('mz/fs')
streamToPromise = require('stream-to-promise')
getFiles = ->
streamToPromise(klaw(dir))
.filter((item) -> not item.stats.isDirectory())
.map((item) -> item.path)
pack = tar.pack()
getFiles(dir)
.map (file) ->
relPath = path.relative(path.resolve(dir), file)
Promise.join relPath, fs.stat(file), fs.readFile(file),
(filename, stats, data) ->
pack.entryAsync({ name: filename, size: stats.size, mode: stats.mode }, data)
.then ->
pack.finalize()
return pack
cacheHighlightStream = ->
colors = require('colors/safe')
es = require('event-stream')
{ EOL } = require('os')
extractArrowMessage = (message) ->
arrowTest = /^\s*-+>\s*(.+)/i
if (match = arrowTest.exec(message))
match[1]
else
undefined
es.mapSync (data) ->
msg = extractArrowMessage(data)
if msg? and msg.toLowerCase() == 'using cache'
data = colors.bgGreen.black(msg)
return data + EOL
parseBuildArgs = (args, onError) ->
_ = require('lodash')
if not _.isArray(args)
args = [ args ]
buildArgs = {}
args.forEach (str) ->
pair = /^([^\s]+?)=(.*)$/.exec(str)
if pair?
buildArgs[pair[1]] = pair[2]
else
onError(str)
return buildArgs
# Pass in the command line parameters and options and also
# a function which will return the information about the bundle
exports.runBuild = (params, options, getBundleInfo, logger) ->
Promise = require('bluebird')
dockerBuild = require('resin-docker-build')
resolver = require('resin-bundle-resolve')
es = require('event-stream')
doodles = require('resin-doodles')
transpose = require('docker-qemu-transpose')
path = require('path')
# The default build context is the current directory
params.source ?= '.'
logs = ''
# Only used in emulated builds
qemuPath = ''
Promise.try ->
return if not (options.emulated and platformNeedsQemu())
hasQemu()
.then (present) ->
if !present
logger.logInfo('Installing qemu for ARM emulation...')
installQemu()
.then ->
# Copy the qemu binary into the build context
copyQemu(params.source)
.then (binPath) ->
qemuPath = path.relative(params.source, binPath)
.then ->
# Tar up the directory, ready for the build stream
tarDirectory(params.source)
.then (tarStream) ->
new Promise (resolve, reject) ->
hooks =
buildSuccess: (image) ->
# Show charlie. In the interest of cloud parity,
# use console.log, not the standard logging streams
doodle = doodles.getDoodle()
console.log()
console.log(doodle)
console.log()
resolve({ image, log: logs + '\n' + doodle + '\n' } )
buildFailure: reject
buildStream: (stream) ->
if options.emulated
logger.logInfo('Running emulated build')
getBundleInfo(options)
.then (info) ->
if !info?
logger.logWarn '''
Warning: No architecture/device type or application information provided.
Dockerfile/project pre-processing will not be performed.
'''
return tarStream
else
[arch, deviceType] = info
# Perform type resolution on the project
bundle = new resolver.Bundle(tarStream, deviceType, arch)
resolver.resolveBundle(bundle, resolver.getDefaultResolvers())
.then (resolved) ->
logger.logInfo("Building #{resolved.projectType} project")
return resolved.tarStream
.then (buildStream) ->
# if we need emulation
if options.emulated and platformNeedsQemu()
return transpose.transposeTarStream buildStream,
hostQemuPath: qemuPath
containerQemuPath: "/tmp/#{QEMU_BIN_NAME}"
else
return buildStream
.then (buildStream) ->
# Send the resolved tar stream to the docker daemon
buildStream.pipe(stream)
.catch(reject)
# And print the output
logThroughStream = es.through (data) ->
logs += data.toString()
this.emit('data', data)
if options.emulated and platformNeedsQemu()
buildThroughStream = transpose.getBuildThroughStream
hostQemuPath: qemuPath
containerQemuPath: "/tmp/#{QEMU_BIN_NAME}"
newStream = stream.pipe(buildThroughStream)
else
newStream = stream
newStream
.pipe(logThroughStream)
.pipe(cacheHighlightStream())
.pipe(logger.streams.build)
# Create a builder
generateConnectOpts(options)
.tap (connectOpts) ->
ensureDockerSeemsAccessible(connectOpts)
.then (connectOpts) ->
# Allow degugging output, hidden behind an env var
logger.logDebug('Connecting with the following options:')
logger.logDebug(JSON.stringify(connectOpts, null, ' '))
builder = new dockerBuild.Builder(connectOpts)
opts = {}
if options.tag?
opts['t'] = options.tag
if options.nocache?
opts['nocache'] = true
if options.buildArg?
opts['buildargs'] = parseBuildArgs options.buildArg, (arg) ->
logger.logWarn("Could not parse variable: '#{arg}'")
if options.squash?
opts['squash'] = true
builder.createBuildStream(opts, hooks, reject)
# Given an image id or tag, export the image to a tar archive,
# gzip the result, and buffer it to disk.
exports.bufferImage = (docker, imageId, bufferFile) ->
Promise = require('bluebird')
streamUtils = require('./streams')
image = docker.getImage(imageId)
imageMetadata = image.inspectAsync()
Promise.join image.get(), imageMetadata.get('Size'), (imageStream, imageSize) ->
streamUtils.buffer(imageStream, bufferFile)
.tap (bufferedStream) ->
bufferedStream.length = imageSize
exports.getDocker = (options) ->
Docker = require('dockerode')
Promise = require('bluebird')
generateConnectOpts(options)
.tap (connectOpts) ->
ensureDockerSeemsAccessible(connectOpts)
.then (connectOpts) ->
# Use bluebird's promises
connectOpts['Promise'] = Promise
new Docker(connectOpts)
ensureDockerSeemsAccessible = (options) ->
fs = require('mz/fs')
if options.socketPath?
# If we're trying to use a socket, check it exists and we have access to it
fs.access(options.socketPath, (fs.constants || fs).R_OK | (fs.constants || fs).W_OK)
.return(true)
.catch (err) ->
throw new Error(
"Docker seems to be unavailable (using socket #{options.socketPath}). Is it
installed, and do you have permission to talk to it?"
)
else
# Otherwise, we think we're probably ok
Promise.resolve(true)
hasQemu = ->
fs = require('mz/fs')
getQemuPath()
.then(fs.stat)
.return(true)
.catchReturn(false)
getQemuPath = ->
resin = require('resin-sdk-preconfigured')
path = require('path')
fs = require('mz/fs')
resin.settings.get('binDirectory')
.then (binDir) ->
# The directory might not be created already,
# if not, create it
fs.access(binDir)
.catch code: 'ENOENT', ->
fs.mkdir(binDir)
.then ->
path.join(binDir, QEMU_BIN_NAME)
platformNeedsQemu = ->
os = require('os')
os.platform() == 'linux'
installQemu = ->
request = require('request')
fs = require('fs')
zlib = require('zlib')
getQemuPath()
.then (qemuPath) ->
new Promise (resolve, reject) ->
installStream = fs.createWriteStream(qemuPath)
qemuUrl = "https://github.com/resin-io/qemu/releases/download/#{QEMU_VERSION}/#{QEMU_BIN_NAME}.gz"
request(qemuUrl)
.pipe(zlib.createGunzip())
.pipe(installStream)
.on('error', reject)
.on('finish', resolve)
copyQemu = (context) ->
path = require('path')
fs = require('mz/fs')
# Create a hidden directory in the build context, containing qemu
binDir = path.join(context, '.resin')
binPath = path.join(binDir, QEMU_BIN_NAME)
fs.access(binDir)
.catch code: 'ENOENT', ->
fs.mkdir(binDir)
.then ->
getQemuPath()
.then (qemu) ->
new Promise (resolve, reject) ->
read = fs.createReadStream(qemu)
write = fs.createWriteStream(binPath)
read
.pipe(write)
.on('error', reject)
.on('finish', resolve)
.then ->
fs.chmod(binPath, '755')
.return(binPath)

135
lib/utils/helpers.coffee Normal file
View File

@ -0,0 +1,135 @@
###
Copyright 2016-2017 Resin.io
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.
###
Promise = require('bluebird')
exports.getGroupDefaults = (group) ->
_ = require('lodash')
return _.chain(group)
.get('options')
.map (question) ->
return [ question.name, question.default ]
.fromPairs()
.value()
exports.stateToString = (state) ->
_str = require('underscore.string')
chalk = require('chalk')
percentage = _str.lpad(state.percentage, 3, '0') + '%'
result = "#{chalk.blue(percentage)} #{chalk.cyan(state.operation.command)}"
switch state.operation.command
when 'copy'
return "#{result} #{state.operation.from.path} -> #{state.operation.to.path}"
when 'replace'
return "#{result} #{state.operation.file.path}, #{state.operation.copy} -> #{state.operation.replace}"
when 'run-script'
return "#{result} #{state.operation.script}"
else
throw new Error("Unsupported operation: #{state.operation.type}")
exports.sudo = (command) ->
_ = require('lodash')
os = require('os')
if os.platform() isnt 'win32'
console.log('If asked please type your computer password to continue')
command = _.union(_.take(process.argv, 2), command)
presidentExecuteAsync = Promise.promisify(require('president').execute)
return presidentExecuteAsync(command)
exports.getManifest = (image, deviceType) ->
rindle = require('rindle')
imagefs = require('resin-image-fs')
resin = require('resin-sdk-preconfigured')
# Attempt to read manifest from the first
# partition, but fallback to the API if
# we encounter any errors along the way.
imagefs.read
image: image
partition:
primary: 1
path: '/device-type.json'
.then(rindle.extractAsync)
.then(JSON.parse)
.catch ->
resin.models.device.getManifestBySlug(deviceType)
exports.osProgressHandler = (step) ->
rindle = require('rindle')
visuals = require('resin-cli-visuals')
step.on('stdout', process.stdout.write.bind(process.stdout))
step.on('stderr', process.stderr.write.bind(process.stderr))
step.on 'state', (state) ->
return if state.operation.command is 'burn'
console.log(exports.stateToString(state))
progressBars =
write: new visuals.Progress('Writing Device OS')
check: new visuals.Progress('Validating Device OS')
step.on 'burn', (state) ->
progressBars[state.type].update(state)
return rindle.wait(step)
exports.getAppInfo = (application) ->
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
Promise.join(
getApplication(application),
resin.models.config.getDeviceTypes(),
(app, config) ->
config = _.find(config, 'slug': app.device_type)
if !config?
throw new Error('Could not read application information!')
app.arch = config.arch
return app
)
getApplication = (application) ->
resin = require('resin-sdk-preconfigured')
# Check for an app of the form `user/application`, and send
# this off to a special handler (before importing any modules)
if (match = /(\w+)\/(\w+)/.exec(application))
return resin.models.application.getAppWithOwner(match[2], match[1])
return resin.models.application.get(application)
# A function to reliably execute a command
# in all supported operating systems, including
# different Windows environments like `cmd.exe`
# and `Cygwin`.
exports.getSubShellCommand = (command) ->
os = require('os')
if os.platform() is 'win32'
return {
program: 'cmd.exe'
args: [ '/s', '/c', command ]
}
else
return {
program: '/bin/sh'
args: [ '-c', command ]
}

46
lib/utils/logger.coffee Normal file
View File

@ -0,0 +1,46 @@
eol = require('os').EOL
module.exports = class Logger
constructor: ->
{ StreamLogger } = require('resin-stream-logger')
colors = require('colors')
_ = require('lodash')
logger = new StreamLogger()
logger.addPrefix('build', colors.blue('[Build]'))
logger.addPrefix('info', colors.cyan('[Info]'))
logger.addPrefix('debug', colors.magenta('[Debug]'))
logger.addPrefix('success', colors.green('[Success]'))
logger.addPrefix('warn', colors.yellow('[Warn]'))
logger.addPrefix('error', colors.red('[Error]'))
@streams =
build: logger.createLogStream('build'),
info: logger.createLogStream('info'),
debug: logger.createLogStream('debug'),
success: logger.createLogStream('success'),
warn: logger.createLogStream('warn'),
error: logger.createLogStream('error')
_.mapKeys @streams, (stream, key) ->
if key isnt 'debug'
stream.pipe(process.stdout)
else
stream.pipe(process.stdout) if process.env.DEBUG?
@formatMessage = logger.formatWithPrefix.bind(logger)
logInfo: (msg) ->
@streams.info.write(msg + eol)
logDebug: (msg) ->
@streams.debug.write(msg + eol)
logSuccess: (msg) ->
@streams.success.write(msg + eol)
logWarn: (msg) ->
@streams.warn.write(msg + eol)
logError: (msg) ->
@streams.error.write(msg + eol)

Some files were not shown because too many files have changed in this diff Show More