From 445e37ccaf11afbf5b06e83d73bc8edb98214a8d Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 1 Oct 2015 13:07:53 -0400 Subject: [PATCH] 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 --- build/actions/device.js | 7 ++++--- build/actions/os.js | 1 + build/actions/wizard.js | 1 - build/utils/helpers.js | 18 +++++++++++++++++- lib/actions/device.coffee | 4 ++-- lib/actions/os.coffee | 1 + lib/actions/wizard.coffee | 1 - lib/utils/helpers.coffee | 16 ++++++++++++++++ 8 files changed, 41 insertions(+), 8 deletions(-) diff --git a/build/actions/device.js b/build/actions/device.js index e7069240..76495f7f 100644 --- a/build/actions/device.js +++ b/build/actions/device.js @@ -1,5 +1,5 @@ (function() { - var Promise, _, capitano, commandOptions, events, form, patterns, resin, rimraf, tmp, visuals; + var Promise, _, capitano, commandOptions, events, form, helpers, patterns, resin, rimraf, tmp, visuals; Promise = require('bluebird'); @@ -19,6 +19,8 @@ patterns = require('../utils/patterns'); + helpers = require('../utils/helpers'); + tmp = Promise.promisifyAll(require('tmp')); tmp.setGracefulCleanup(); @@ -131,7 +133,6 @@ help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNotice this command may ask for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp', options: [commandOptions.optionalApplication, commandOptions.yes], permission: 'user', - root: true, action: function(params, options, done) { return Promise["try"](function() { if (options.application != null) { @@ -148,7 +149,7 @@ return Promise.using(download(), function(temporalPath) { return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) { return capitano.runAsync("os configure " + temporalPath + " " + device.uuid).then(function() { - return capitano.runAsync("os initialize " + temporalPath + " " + application.device_type); + return helpers.sudo(['os', 'initialize', temporalPath, application.device_type]); }); }); }).then(function(device) { diff --git a/build/actions/os.js b/build/actions/os.js index 71df911d..7a32990e 100644 --- a/build/actions/os.js +++ b/build/actions/os.js @@ -102,6 +102,7 @@ description: 'initialize an os image', help: 'Use this command to initialize a previously configured operating system image.\n\nExamples:\n\n $ resin os initialize ../path/rpi.img \'raspberry-pi\'', permission: 'user', + root: true, action: function(params, options, done) { console.info('Initializing device'); return resin.models.device.getManifestBySlug(params.type).then(function(manifest) { diff --git a/build/actions/wizard.js b/build/actions/wizard.js index 76ede468..3ccd91a6 100644 --- a/build/actions/wizard.js +++ b/build/actions/wizard.js @@ -17,7 +17,6 @@ signature: 'quickstart [name]', description: 'getting started with resin.io', help: 'Use this command to run a friendly wizard to get started with resin.io.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ sudo resin quickstart\n $ sudo resin quickstart MyApp', - root: true, permission: 'user', action: function(params, options, done) { return Promise["try"](function() { diff --git a/build/utils/helpers.js b/build/utils/helpers.js index ca028624..541ff3a8 100644 --- a/build/utils/helpers.js +++ b/build/utils/helpers.js @@ -1,12 +1,16 @@ (function() { - var Promise, _, chalk, os; + var Promise, _, capitano, chalk, child_process, os; Promise = require('bluebird'); + capitano = Promise.promisifyAll(require('capitano')); + _ = require('lodash'); _.str = require('underscore.string'); + child_process = require('child_process'); + os = require('os'); chalk = require('chalk'); @@ -44,4 +48,16 @@ }); }; + exports.sudo = function(command) { + var spawn; + if (os.platform() === 'win32') { + return capitano.runAsync(command.join(' ')); + } + command = _.union(_.take(process.argv, 2), command); + spawn = child_process.spawn('sudo', command, { + stdio: 'inherit' + }); + return exports.waitStream(spawn); + }; + }).call(this); diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index 775b60b8..4c1b400e 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -7,6 +7,7 @@ form = require('resin-cli-form') events = require('resin-cli-events') rimraf = Promise.promisify(require('rimraf')) patterns = require('../utils/patterns') +helpers = require('../utils/helpers') tmp = Promise.promisifyAll(require('tmp')) tmp.setGracefulCleanup() @@ -188,7 +189,6 @@ exports.init = commandOptions.yes ] permission: 'user' - root: true action: (params, options, done) -> Promise.try -> return options.application if options.application? @@ -206,7 +206,7 @@ exports.init = .then(resin.models.device.get) .tap (device) -> capitano.runAsync("os configure #{temporalPath} #{device.uuid}").then -> - capitano.runAsync("os initialize #{temporalPath} #{application.device_type}") + helpers.sudo([ 'os', 'initialize', temporalPath, application.device_type ]) .then (device) -> console.log('Done') return device.uuid diff --git a/lib/actions/os.coffee b/lib/actions/os.coffee index cdc79fbb..1f2cf160 100644 --- a/lib/actions/os.coffee +++ b/lib/actions/os.coffee @@ -105,6 +105,7 @@ exports.initialize = $ resin os initialize ../path/rpi.img 'raspberry-pi' ''' permission: 'user' + root: true action: (params, options, done) -> console.info('Initializing device') resin.models.device.getManifestBySlug(params.type) diff --git a/lib/actions/wizard.coffee b/lib/actions/wizard.coffee index 38345835..0a7f15a2 100644 --- a/lib/actions/wizard.coffee +++ b/lib/actions/wizard.coffee @@ -23,7 +23,6 @@ exports.wizard = $ sudo resin quickstart $ sudo resin quickstart MyApp ''' - root: true permission: 'user' action: (params, options, done) -> Promise.try -> diff --git a/lib/utils/helpers.coffee b/lib/utils/helpers.coffee index 27e11ac8..d97c7856 100644 --- a/lib/utils/helpers.coffee +++ b/lib/utils/helpers.coffee @@ -1,6 +1,8 @@ Promise = require('bluebird') +capitano = Promise.promisifyAll(require('capitano')) _ = require('lodash') _.str = require('underscore.string') +child_process = require('child_process') os = require('os') chalk = require('chalk') @@ -28,3 +30,17 @@ exports.waitStream = (stream) -> stream.on('close', resolve) stream.on('end', resolve) stream.on('error', reject) + +exports.sudo = (command) -> + + # Bypass privilege elevation for Windows for now. + # We should use `windosu` in this case. + if os.platform() is 'win32' + return capitano.runAsync(command.join(' ')) + + command = _.union(_.take(process.argv, 2), command) + + spawn = child_process.spawn 'sudo', command, + stdio: 'inherit' + + return exports.waitStream(spawn)