diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1a95dcc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 MeteorHacks Pvt Ltd (Sri Lanka). + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index 903ba0e..3a2546b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,30 @@ # Meteor Up +> We are working on the next version of Meteor Up with more stability and fast deployments. +> For that we use Docker behind the scene. +> Check it out: https://github.com/arunoda/meteor-up/tree/mupx# + #### Production Quality Meteor Deployments Meteor Up (mup for short) is a command line tool that allows you to deploy any [Meteor](http://meteor.com) app to your own server. It supports only Debian/Ubuntu flavours and Open Solaris at the moments. (PRs are welcome) -> Screencast: [How to deploy a Meteor app with Meteor Up (by Sacha Grief)](https://www.youtube.com/watch?v=WLGdXtZMmiI) +You can use install and use Meteor Up from Linux, Mac and Windows. + +> Screencast: [How to deploy a Meteor app with Meteor Up (by Sacha Greif)](https://www.youtube.com/watch?v=WLGdXtZMmiI) **Table of Contents** - [Features](#features) - [Server Configuration](#server-configuration) + - [SSH-key-based authentication (with passphrase)](#ssh-keys-with-passphrase-or-ssh-agent-support) - [Nginx Multiple App Hosting](#server-nginx) - [Installation](#installation) - [Creating a Meteor Up Project](#creating-a-meteor-up-project) - [Example File](#example-file) - [Setting Up a Server](#setting-up-a-server) - - [Server Setup Details](#server-setup-details) - [Deploying an App](#deploying-an-app) +- [Additional Setup/Deploy Information](#additional-setupdeploy-information) + - [Server Setup Details](#server-setup-details) - [Deploy Wait Time](#deploy-wait-time) - [Multiple Deployment Targets](#multiple-deployment-targets) - [Access Logs](#access-logs) @@ -63,8 +71,6 @@ Meteor Up (mup for short) is a command line tool that allows you to deploy any [ npm install -g mup -If you are looking for password-based authentication, you need to [install sshpass](https://gist.github.com/arunoda/7790979) on your local development machine. - ### Creating a Meteor Up Project mkdir ~/my-meteor-deployment @@ -87,12 +93,11 @@ This will create two files in your Meteor Up project directory: { "host": "hostname", "username": "root", - "password": "password" + "password": "password", // or pem file (ssh based authentication) - // WARNING: Keys protected by a passphrase are not supported - //"pem": "~/.ssh/id_rsa" + //"pem": "~/.ssh/id_rsa", // Also, for non-standard ssh port use this - //"sshOptions": { "Port" : 49154 }, + //"sshOptions": { "port" : 49154 }, // server specific environment variables "env": {} } @@ -104,12 +109,16 @@ This will create two files in your Meteor Up project directory: // WARNING: Node.js is required! Only skip if you already have Node.js installed on server. "setupNode": true, - // WARNING: nodeVersion defaults to 0.10.33 if omitted. Do not use v, just the version number. - "nodeVersion": "0.10.33", + // WARNING: nodeVersion defaults to 0.10.36 if omitted. Do not use v, just the version number. + "nodeVersion": "0.10.36", // Install PhantomJS on the server "setupPhantom": true, + // Show a progress bar during the upload of the bundle to the server. + // Might cause an error in some rare cases if set to true, for instance in Shippable CI + "enableUploadProgressBar": true, + // Install Nginx on the server (Requires a unique PORT and ROOT_URL in the environment settings // Note: Port must not be 80 as Nginx will proxy_forward to port 80 per ROOT_URL (e.g. 3000, 3001, 3002, etc) "setupNginx": true, @@ -144,10 +153,42 @@ This will create two files in your Meteor Up project directory: This will setup the server for the `mup` deployments. It will take around 2-5 minutes depending on the server's performance and network availability. -#### Ssh based authentication +### Deploying an App + + mup deploy + +This will bundle the Meteor project and deploy it to the server. + +### Additional Setup/Deploy Information + +#### Deploy Wait Time + +Meteor Up checks if the deployment is successful or not just after the deployment. By default, it will wait 10 seconds before the check. You can configure the wait time with the `deployCheckWaitTime` option in the `mup.json` + +#### SSH keys with passphrase (or ssh-agent support) + +> This only tested with Mac/Linux + +With the help of `ssh-agent`, `mup` can use SSH keys encrypted with a +passphrase. + +Here's the process: + +* First remove your `pem` field from the `mup.json`. So, your `mup.json` only has the username and host only. +* Then start a ssh agent with `eval $(ssh-agent)` +* Then add your ssh key with `ssh-add ` +* Then you'll asked to enter the passphrase to the key +* After that simply invoke `mup` commands and they'll just work +* Once you've deployed your app kill the ssh agent with `ssh-agent -k` + +#### Ssh based authentication with `sudo` + +**If your username is `root`, you don't need to follow these steps** Please ensure your key file (pem) is not protected by a passphrase. Also the setup process will require NOPASSWD access to sudo. (Since Meteor needs port 80, sudo access is required.) +Make sure you also add your ssh key to the ```/YOUR_USERNAME/.ssh/authorized_keys``` list + You can add your user to the sudo group: sudo adduser *username* sudo @@ -179,16 +220,6 @@ This is how Meteor Up will configure the server for you based on the given `appN For more information see [`lib/taskLists.js`](https://github.com/arunoda/meteor-up/blob/master/lib/taskLists.js). -### Deploying an App - - mup deploy - -This will bundle the Meteor project and deploy it to the server. - -#### Deploy Wait Time - -Meteor Up checks if the deployment is successful or not just after the deployment. By default, it will wait 10 seconds before the check. You can configure the wait time with the `deployCheckWaitTime` option in the `mup.json`. - #### Multiple Deployment Targets You can use an array to deploy to multiple servers at once. @@ -241,7 +272,7 @@ It is possible to provide server specific environment variables. Add the `env` o { "host": "hostname", "username": "root", - "password": "password" + "password": "password", "env": { "SOME_ENV": "the-value" } @@ -303,7 +334,7 @@ Meteor Up has the built in SSL support. It uses [stud](https://github.com/bumpte * When asked to select your SSL server type, select it as nginx. * Then you'll get a set of files (your domain certificate and CA files). -Now you need combine SSL certificate(s) with the private key and save it in the mup config directory as `ssl.pem`. Check this [guide](http://alexnj.com/blog/configuring-a-positivessl-certificate-with-stud.html) to do that. +Now you need combine SSL certificate(s) with the private key and save it in the mup config directory as `ssl.pem`. Check this [guide](http://alexnj.com/blog/configuring-a-positivessl-certificate-with-stud) to do that. Then add following configuration to your `mup.json` file. @@ -337,6 +368,10 @@ You should try and keep `mup` up to date in order to keep up with the latest Met ### Troubleshooting +#### Check Access + +Your issue might not always be related to Meteor Up. So make sure you can connect to your instance first, and that your credentials are working properly. + #### Check Logs If you suddenly can't deploy your app anymore, first use the `mup logs -f` command to check the logs for error messages. diff --git a/bin/mup b/bin/mup index be0d346..7136c21 100755 --- a/bin/mup +++ b/bin/mup @@ -18,21 +18,7 @@ if(action == 'init') { var cwd = path.resolve('.'); //read config and validate it var config = Config.read(); - - if(config._passwordExists) { - //check for sshpass - helpers.checkSshPassExists(function(exists) { - if(exists) { - runActions(config, cwd); - } else { - var errorMessage = "sshpass required for password based authentication: refer http://git.io/_vHbvQ"; - console.error(errorMessage.bold.red); - process.exit(1); - } - }); - } else { - runActions(config, cwd); - } + runActions(config, cwd); } diff --git a/example/mup.json b/example/mup.json index ba7deac..9166447 100644 --- a/example/mup.json +++ b/example/mup.json @@ -16,8 +16,8 @@ // WARNING: Node.js is required! Only skip if you already have Node.js installed on server. "setupNode": true, - // WARNING: If nodeVersion omitted will setup 0.10.33 by default. Do not use v, only version number. - "nodeVersion": "0.10.33", + // WARNING: If nodeVersion omitted will setup 0.10.36 by default. Do not use v, only version number. + "nodeVersion": "0.10.36", // Install PhantomJS in the server "setupPhantom": true, @@ -26,6 +26,10 @@ // Note: Port must not be 80 as Nginx will proxy_forward to port 80 per ROOT_URL (e.g. 3000, 3001, 3002, etc) "setupNginx": false, + // Show a progress bar during the upload of the bundle to the server. + // Might cause an error in some rare cases if set to true, for instance in Shippable CI + "enableUploadProgressBar": true, + // Application name (No spaces) "appName": "meteor", diff --git a/lib/actions.js b/lib/actions.js index 4bf4320..f9354bc 100644 --- a/lib/actions.js +++ b/lib/actions.js @@ -8,6 +8,9 @@ var uuid = require('uuid'); var format = require('util').format; var extend = require('util')._extend; var _ = require('underscore'); +var async = require('async'); +var buildApp = require('./build.js'); +var os = require('os'); require('colors'); module.exports = Actions; @@ -26,9 +29,6 @@ function Actions(config, cwd) { Actions.prototype._createSessionsMap = function(config) { var sessionsMap = {}; - var options = { - ssh: {'StrictHostKeyChecking': 'no', 'UserKnownHostsFile': '/dev/null'} - }; config.servers.forEach(function(server) { var host = server.host; @@ -40,11 +40,10 @@ Actions.prototype._createSessionsMap = function(config) { auth.password = server.password; } - if(server.sshOptions) { - for(var key in server.sshOptions) { - options.ssh[key] = server.sshOptions[key]; - } - } + var nodemiralOptions = { + ssh: server.sshOptions, + keepAlive: true + }; if(!sessionsMap[server.os]) { sessionsMap[server.os] = { @@ -53,7 +52,7 @@ Actions.prototype._createSessionsMap = function(config) { }; } - var session = nodemiral.session(host, auth, options); + var session = nodemiral.session(host, auth, nodemiralOptions); session._serverConfig = server; sessionsMap[server.os].sessions.push(session); }); @@ -75,100 +74,122 @@ Actions.prototype._showKadiraLink = function() { ); } } -} +}; -Actions.prototype.setup = function() { +Actions.prototype._executePararell = function(actionName, args) { var self = this; - self._showKadiraLink(); + var sessionInfoList = _.values(self.sessionsMap); + async.map( + sessionInfoList, + function(sessionsInfo, callback) { + var taskList = sessionsInfo.taskListsBuilder[actionName] + .apply(sessionsInfo.taskListsBuilder, args); + taskList.run(sessionsInfo.sessions, function(summaryMap) { + callback(null, summaryMap); + }); + }, + whenAfterCompleted + ); +}; - for(var os in self.sessionsMap) { - var sessionsInfo = self.sessionsMap[os]; - var taskList = sessionsInfo.taskListsBuilder.setup(self.config); - taskList.run(sessionsInfo.sessions); - } +Actions.prototype.setup = function() { + this._showKadiraLink(); + this._executePararell("setup", [this.config]); }; Actions.prototype.deploy = function() { var self = this; self._showKadiraLink(); - var buildLocation = path.resolve('/tmp', uuid.v4()); + var buildLocation = path.resolve(os.tmpdir(), uuid.v4()); var bundlePath = path.resolve(buildLocation, 'bundle.tar.gz'); - var options = { - cwd: this.config.app, - }; // spawn inherits env vars from process.env // so we can simply set them like this process.env.BUILD_LOCATION = buildLocation; - var buildScript = path.resolve(__dirname, 'build.sh'); var deployCheckWaitTime = this.config.deployCheckWaitTime; var appName = this.config.appName; var setupNginx = this.config.setupNginx || false; var ssl = this.config.ssl || false; + var appPath = this.config.app; + var enableUploadProgressBar = this.config.enableUploadProgressBar; + var meteorBinary = this.config.meteorBinary; + var setupNginx = this.config.setupNginx || false; console.log('Building Started: ' + this.config.app); - buildApp(buildScript, options, function(err) { + buildApp(appPath, meteorBinary, buildLocation, function(err) { if(err) { process.exit(1); } else { - for(var os in self.sessionsMap) { - var sessionsInfo = self.sessionsMap[os]; - sessionsInfo.sessions.forEach(function(session) { + var sessionsData = []; + _.forEach(self.sessionsMap, function (sessionsInfo) { + var taskListsBuilder = sessionsInfo.taskListsBuilder; + _.forEach(sessionsInfo.sessions, function (session) { + sessionsData.push({ + taskListsBuilder: taskListsBuilder, + session: session + }); + }); + }); + + async.mapSeries( + sessionsData, + function (sessionData, callback) { + var session = sessionData.session; + var taskListsBuilder = sessionData.taskListsBuilder; var env = _.extend({}, self.config.env, session._serverConfig.env); - var taskList = sessionsInfo.taskListsBuilder.deploy( + var taskList = taskListsBuilder.deploy( bundlePath, env, - deployCheckWaitTime, appName, setupNginx, ssl); - taskList.run(session, afterCompleted); - }); - } + deployCheckWaitTime, appName, setupNginx, ssl, enableUploadProgressBar); + taskList.run(session, function (summaryMap) { + rimraf.sync(buildLocation); + callback(null, summaryMap); + }); + }, + whenAfterDeployed(buildLocation) + ); } }); - - function afterCompleted(summeryMap) { - rimraf.sync(buildLocation); - } }; Actions.prototype.reconfig = function() { var self = this; + var sessionInfoList = []; for(var os in self.sessionsMap) { var sessionsInfo = self.sessionsMap[os]; sessionsInfo.sessions.forEach(function(session) { var env = _.extend({}, self.config.env, session._serverConfig.env); var taskList = sessionsInfo.taskListsBuilder.reconfig( env, self.config.appName, self.config.setupNginx); - taskList.run(session); + sessionInfoList.push({ + taskList: taskList, + session: session + }); }); } + + async.mapSeries( + sessionInfoList, + function(sessionInfo, callback) { + sessionInfo.taskList.run(sessionInfo.session, function(summaryMap) { + callback(null, summaryMap); + }); + }, + whenAfterCompleted + ); }; Actions.prototype.restart = function() { - var self = this; - for(var os in self.sessionsMap) { - var sessionsInfo = self.sessionsMap[os]; - var taskList = sessionsInfo.taskListsBuilder.restart(self.config.appName); - taskList.run(sessionsInfo.sessions); - } + this._executePararell("restart", [this.config.appName]); }; Actions.prototype.stop = function() { - var self = this; - for(var os in self.sessionsMap) { - var sessionsInfo = self.sessionsMap[os]; - var taskList = sessionsInfo.taskListsBuilder.stop(self.config.appName); - taskList.run(sessionsInfo.sessions); - } + this._executePararell("stop", [this.config.appName]); }; Actions.prototype.start = function() { - var self = this; - for(var os in self.sessionsMap) { - var sessionsInfo = self.sessionsMap[os]; - var taskList = sessionsInfo.taskListsBuilder.start(self.config.appName); - taskList.run(sessionsInfo.sessions); - } + this._executePararell("start", [this.config.appName]); }; Actions.prototype.logs = function() { @@ -223,45 +244,33 @@ Actions.init = function() { } }; - -function buildApp(buildScript, options, callback) { - var vars = { - stdout: "", - stderr: "", - error: "" - }; - - var bash = spawn("bash", [buildScript], options); - bash.stdout.on('data', storeLastNChars(vars, "stdout", 1000)); - bash.stderr.on('data', storeLastNChars(vars, "stderr", 1000)); - bash.on('error', function(err) { - vars.error = err.message; - }); - bash.on('close', function(code) { - //clear callback - bash.stdout.removeAllListeners('data'); - bash.stderr.removeAllListeners('data'); - bash.removeAllListeners('error'); - bash.removeAllListeners('close'); - - if(code != 0) { - console.error(format('Bundling Error: code=%s, error:%s', code, vars.error)); - console.error('-------------------STDOUT-------------------'.bold); - console.error(vars.stdout); - console.error('-------------------STDERR-------------------'.bold.red); - console.error(vars.stderr.red); - callback({message: vars.error}); - } else { - callback(null); - } - }); -} - function storeLastNChars(vars, field, limit, color) { return function(data) { vars[field] += data.toString(); if(vars[field].length > 1000) { vars[field] = vars[field].substring(vars[field].length - 1000); } - } + }; +} + +function whenAfterDeployed(buildLocation) { + return function(error, summaryMaps) { + rimraf.sync(buildLocation); + whenAfterCompleted(error, summaryMaps); + }; +} + +function whenAfterCompleted(error, summaryMaps) { + var errorCode = error || haveSummaryMapsErrors(summaryMaps) ? 1 : 0; + process.exit(errorCode); +} + +function haveSummaryMapsErrors(summaryMaps) { + return _.some(summaryMaps, hasSummaryMapErrors); +} + +function hasSummaryMapErrors(summaryMap) { + return _.some(summaryMap, function (summary) { + return summary.error; + }); } diff --git a/lib/build.js b/lib/build.js new file mode 100644 index 0000000..c266d6a --- /dev/null +++ b/lib/build.js @@ -0,0 +1,68 @@ +var spawn = require('child_process').spawn; +var archiver = require('archiver'); +var fs = require('fs'); +var pathResolve = require('path').resolve; +var _ = require('underscore'); + +function buildApp(appPath, meteorBinary, buildLocaltion, callback) { + buildMeteorApp(appPath, meteorBinary, buildLocaltion, function(code) { + if(code == 0) { + archiveIt(buildLocaltion, callback); + } else { + console.log("\n=> Build Error. Check the logs printed above."); + callback(new Error("build-error")); + } + }); +} + +function buildMeteorApp(appPath, meteorBinary, buildLocaltion, callback) { + var executable = meteorBinary; + var args = [ + "build", "--directory", buildLocaltion, + "--server", "http://localhost:3000" + ]; + + var isWin = /^win/.test(process.platform); + if(isWin) { + // Sometimes cmd.exe not available in the path + // See: http://goo.gl/ADmzoD + executable = process.env.comspec || "cmd.exe"; + args = ["/c", "meteor"].concat(args); + } + + var options = {cwd: appPath}; + var meteor = spawn(executable, args, options); + var stdout = ""; + var stderr = ""; + + meteor.stdout.pipe(process.stdout, {end: false}); + meteor.stderr.pipe(process.stderr, {end: false}); + + meteor.on('close', callback); +} + +function archiveIt(buildLocaltion, callback) { + callback = _.once(callback); + var bundlePath = pathResolve(buildLocaltion, 'bundle.tar.gz'); + var sourceDir = pathResolve(buildLocaltion, 'bundle'); + + var output = fs.createWriteStream(bundlePath); + var archive = archiver('tar', { + gzip: true, + gzipOptions: { + level: 6 + } + }); + + archive.pipe(output); + output.once('close', callback); + + archive.once('error', function(err) { + console.log("=> Archiving failed:", err.message); + callback(err); + }); + + archive.directory(sourceDir, 'bundle').finalize(); +} + +module.exports = buildApp; diff --git a/lib/build.sh b/lib/build.sh deleted file mode 100644 index 563c8b3..0000000 --- a/lib/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -CURR_DIR=$(pwd) - -setTemporaryPlatforms() { - cp ./.meteor/platforms ./.meteor/platforms-copy - echo server$'\n'browser$'\n'firefoxos > ./.meteor/platforms -} - -setOriginalPlatforms() { - cd $CURR_DIR - mv ./.meteor/platforms-copy ./.meteor/platforms -} - -buildMeteorApp() { - meteor build --directory $BUILD_LOCATION -} - -buildMeteorAppForCordova() { - # when building for cordova, we are using firefoxos - # and it does not need ios and android deps to build web.cordova - # so it's possible to build for the server bundle inside a build box as well - - setTemporaryPlatforms - trap setOriginalPlatforms EXIT - # we need to give it a dummy --server values to start building - meteor build --directory $BUILD_LOCATION --server "http://localhost:3000" -} - -if [ -f ./.meteor/platforms ]; then - # build for 0.9.4+ - # remove mobile platforms while building - have_cordova=$(cat ./.meteor/platforms | grep 'ios\|android\|firefoxos') - if [ "$have_cordova" ]; then - buildMeteorAppForCordova - else - buildMeteorApp - fi -else - # build for 0.9.3 - buildMeteorApp -fi - -cd $BUILD_LOCATION -tar czf bundle.tar.gz bundle \ No newline at end of file diff --git a/lib/config.js b/lib/config.js index bcb5ca8..c0120c1 100644 --- a/lib/config.js +++ b/lib/config.js @@ -27,6 +27,9 @@ exports.read = function() { if(typeof mupJson.appName === "undefined") { mupJson.appName = "meteor"; } + if(typeof mupJson.enableUploadProgressBar === "undefined") { + mupJson.enableUploadProgressBar = true; + } // make sure a PORT and ROOT_URL is specified for Nginx config if (mupJson.setupNginx) { @@ -41,12 +44,20 @@ exports.read = function() { mupErrorLog('Server information does not exist'); } else { mupJson.servers.forEach(function(server) { + var sshAgentExists = false; + var sshAgent = process.env.SSH_AUTH_SOCK; + if(sshAgent) { + sshAgentExists = fs.existsSync(sshAgent); + server.sshOptions = server.sshOptions || {}; + server.sshOptions.agent = sshAgent; + } + if(!server.host) { mupErrorLog('Server host does not exist'); } else if(!server.username) { mupErrorLog('Server username does not exist'); - } else if(!server.password && !server.pem) { - mupErrorLog('Server password or pem does not exist'); + } else if(!server.password && !server.pem && !sshAgentExists) { + mupErrorLog('Server password, pem or a ssh agent does not exist'); } else if(!mupJson.app) { mupErrorLog('Path to app does not exist'); } @@ -55,9 +66,6 @@ exports.read = function() { if(server.pem) { server.pem = rewriteHome(server.pem); - } else { - //hint mup bin script to check whether sshpass installed or not - mupJson._passwordExists = true; } server.env = server.env || {}; @@ -101,7 +109,11 @@ exports.read = function() { }; function rewriteHome(location) { - return location.replace('~', process.env.HOME); + if(/^win/.test(process.platform)) { + return location.replace('~', process.env.USERPROFILE); + } else { + return location.replace('~', process.env.HOME); + } } function mupErrorLog(message) { diff --git a/lib/helpers.js b/lib/helpers.js index 6a01242..7c501a3 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,15 +1,5 @@ var exec = require('child_process').exec; -exports.checkSshPassExists = function(callback) { - exec('sshpass -V', function(err, stdout, stderr) { - if(err) { - callback(false); - } else { - callback(true); - } - }); -}; - exports.printHelp = function() { console.error('\nValid Actions'); console.error('-------------'); diff --git a/lib/taskLists/linux.js b/lib/taskLists/linux.js index 5cea09b..51674fb 100644 --- a/lib/taskLists/linux.js +++ b/lib/taskLists/linux.js @@ -74,7 +74,7 @@ exports.setup = function(config) { return taskList; }; -exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName, setupNginx, ssl) { +exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName, setupNginx, ssl, enableUploadProgressBar) { var taskList = nodemiral.taskList("Deploy app '" + appName + "' (linux)"); if (setupNginx) { @@ -121,7 +121,8 @@ exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName, setupNg vars: { appName: appName, appPort: env['PORT'], - appRoot: env['ROOT_URL'].replace(/.*?:\/\//g, "") + appRoot: env['ROOT_URL'].replace(/.*?:\/\//g, ""), + hostName: ((env['ROOT_URL']).match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i) || [,''])[1] } }); @@ -137,7 +138,8 @@ exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName, setupNg taskList.copy('Uploading bundle', { src: bundlePath, - dest: '/opt/' + appName + '/tmp/bundle.tar.gz' + dest: '/opt/' + appName + '/tmp/bundle.tar.gz', + progressBar: enableUploadProgressBar }); taskList.copy('Setting up Environment Variables', { @@ -207,6 +209,26 @@ exports.reconfig = function(env, appName, setupNginx) { command: '(sudo stop ' + appName + ' || :) && (sudo start ' + appName + ')' }); + if (setupNginx) { + //configure nginx + taskList.copy('Configuring Nginx (App Instance)', { + src: path.resolve(TEMPLATES_DIR, 'nginx.conf'), + dest: '/etc/nginx/sites-available/' + appName + '.conf', + vars: { + appPort: env['PORT'] || 80, + appRoot: env['ROOT_URL'].replace(/.*?:\/\//g, "") + } + }); + + taskList.executeScript('Configuring Nginx (Enabling Site)', { + script: path.resolve(SCRIPT_DIR, 'setup-nginx.sh'), + vars: { + appName: appName + } + }); + + } + return taskList; }; @@ -262,6 +284,7 @@ function configureStud(taskList, pemFilePath, port) { dest: '/opt/stud/ssl.pem' }); + taskList.copy('Configuring Stud', { src: path.resolve(TEMPLATES_DIR, 'stud.conf'), dest: '/opt/stud/stud.conf', @@ -270,8 +293,12 @@ function configureStud(taskList, pemFilePath, port) { } }); + taskList.execute('Verifying SSL Configurations (ssl.pem)', { + command: 'stud --test --config=/opt/stud/stud.conf' + }); + //restart stud - taskList.execute('Strating Stud', { + taskList.execute('Starting Stud', { command: '(sudo stop stud || :) && (sudo start stud || :)' }); } diff --git a/package.json b/package.json index c2dad27..2f24343 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,27 @@ { - "name": "mup", - "version": "0.9.1", - "description": "Production Quality Meteor Deployments", + "name": "mup-multi", + "version": "0.11.4", + "description": "Production Quality Meteor Deployments using Nginx", "dependencies": { - "nodemiral": "0.3.x", + "async": "^0.9.0", "cjson": "0.3.x", - "uuid": "1.4.x", "colors": "0.6.x", + "nodemiral": "^1.1.1", "rimraf": "2.x.x", - "underscore": "1.7.0" + "underscore": "1.7.0", + "uuid": "1.4.x", + "archiver": "0.14.x" }, "bin": { - "mup": "./bin/mup" + "mup-multi": "./bin/mup" }, - "author": "Arunoda Susiripala ", + "author": [ + "Arunoda Susiripala ", + "Tim Brandin " + ], "repository": { "type": "git", - "url": "git://github.com/arunoda/meteor-up.git" + "url": "git://github.com/studiointeract/meteor-up-multi.git" }, "license": "MIT" } diff --git a/scripts/linux/install-nginx.sh b/scripts/linux/install-nginx.sh index 2dca8b7..5811ea3 100644 --- a/scripts/linux/install-nginx.sh +++ b/scripts/linux/install-nginx.sh @@ -13,3 +13,5 @@ sudo apt-get install nginx -y # Restart nginx sudo service nginx stop || : sudo service nginx start + +sudo chmod o+w /usr/share/nginx/html/index.html diff --git a/scripts/linux/install-node.sh b/scripts/linux/install-node.sh index 1b796ba..ba63ec4 100644 --- a/scripts/linux/install-node.sh +++ b/scripts/linux/install-node.sh @@ -14,7 +14,7 @@ sudo apt-get update <% if (nodeVersion) { %> NODE_VERSION=<%= nodeVersion %> <% } else {%> - NODE_VERSION=0.10.33 + NODE_VERSION=0.10.36 <% } %> ARCH=$(python -c 'import platform; print platform.architecture()[0]') diff --git a/scripts/linux/setup-env-nginx.sh b/scripts/linux/setup-env-nginx.sh index d2d25bd..59110ad 100644 --- a/scripts/linux/setup-env-nginx.sh +++ b/scripts/linux/setup-env-nginx.sh @@ -7,3 +7,11 @@ sudo mkdir -p /opt/<%= appName %>/tmp sudo mkdir -p /opt/<%= appName %>/ssl sudo chown ${USER} /opt/<%= appName %> -R + +sudo chmod o-w /usr/share/nginx/html/index.html +sudo chmod -R a+w /etc/nginx/sites-available/ +sudo chmod -R a+w /etc/nginx/sites-enabled/ +sudo chmod -R a+w /var/log/nginx/ + +sudo chown ${USER} /run/nginx.pid +sudo chmod u+rwx /run/nginx.pid diff --git a/scripts/linux/setup-nginx.sh b/scripts/linux/setup-nginx.sh index 6eb8a17..973d2e7 100644 --- a/scripts/linux/setup-nginx.sh +++ b/scripts/linux/setup-nginx.sh @@ -40,7 +40,7 @@ if [ -r $SITE_AVAILABLE ]; then fi ## Test for a well formed configuration. echo "Testing nginx configuration..." - $NGINX -t && STATUS=0 + sudo $NGINX -t && STATUS=0 if [ $STATUS ]; then echo -n "Site $1 has been enabled. " else @@ -52,7 +52,7 @@ else fi echo "Testing nginx configuration..." -$NGINX -t && STATUS=0 +sudo $NGINX -t && STATUS=0 if [ $STATUS ]; then echo -n "Site $1 has been enabled. " else @@ -60,3 +60,6 @@ else fi sudo service nginx reload + +sudo chmod -R og-w /etc/nginx/sites-available/ +sudo chmod -R og-w /etc/nginx/sites-enabled/ diff --git a/scripts/sunos/install-node.sh b/scripts/sunos/install-node.sh index 3320694..1a615ff 100644 --- a/scripts/sunos/install-node.sh +++ b/scripts/sunos/install-node.sh @@ -4,7 +4,7 @@ <% if (nodeVersion) { %> NODE_VERSION=<%= nodeVersion %> <% } else {%> - NODE_VERSION=0.10.33 + NODE_VERSION=0.10.36 <% } %> ARCH=$(python -c 'import platform; print platform.architecture()[0]') diff --git a/templates/linux/deploy.sh b/templates/linux/deploy.sh old mode 100644 new mode 100755 index c1895b0..5f05332 --- a/templates/linux/deploy.sh +++ b/templates/linux/deploy.sh @@ -4,23 +4,46 @@ gyp_rebuild_inside_node_modules () { for npmModule in ./*; do cd $npmModule - if [ -f binding.gyp ]; then - echo "=> re-installing binary npm module '${npmModule:2}' of package '${package:2}'" - sudo node-gyp rebuild - fi + isBinaryModule="no" # recursively rebuild npm modules inside node_modules - if [ -d ./node_modules ]; then - cd ./node_modules - gyp_rebuild_inside_node_modules - cd ../ + check_for_binary_modules () { + if [ -f binding.gyp ]; then + isBinaryModule="yes" + fi + + if [ $isBinaryModule != "yes" ]; then + if [ -d ./node_modules ]; then + cd ./node_modules + for module in ./*; do + cd $module + check_for_binary_modules + cd .. + done + cd ../ + fi + fi + } + + check_for_binary_modules + + if [ $isBinaryModule = "yes" ]; then + echo " > $npmModule: npm install due to binary npm modules" + rm -rf node_modules + if [ -f binding.gyp ]; then + sudo npm install + sudo node-gyp rebuild || : + else + sudo npm install + fi fi + cd .. done } rebuild_binary_npm_modules () { - for package in ./*; do + for package in ./*; do if [ -d $package/node_modules ]; then cd $package/node_modules gyp_rebuild_inside_node_modules @@ -58,6 +81,7 @@ BUNDLE_DIR=${TMP_DIR}/bundle cd ${TMP_DIR} sudo rm -rf bundle sudo tar xvzf bundle.tar.gz > /dev/null +sudo chmod -R +x * sudo chown -R ${USER} ${BUNDLE_DIR} # rebuilding fibers diff --git a/templates/linux/nginx.conf b/templates/linux/nginx.conf index 5f543a5..49cd8fb 100644 --- a/templates/linux/nginx.conf +++ b/templates/linux/nginx.conf @@ -13,14 +13,15 @@ server { proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } - - ## serve static files by nginx instead of Meteor (the public/ folder) - Thanks @yauh :) - #location ~ \.(jpg|jpeg|png|gif|mp3|ico|pdf) { - # root /opt/<%= appName %>/app/programs/web.browser/app; # this should point at the content from the public folder - # access_log off; - # expires 30d; - # add_header Pragma public; - # add_header Cache-Control "public"; - #} - } +<% if (/^www\./i.test(hostName)) { %> +server { + server_name <%= hostName.replace(/^www\./i, '') %>; + return 301 $scheme://<%= hostName %>$request_uri; +} +<% } else { %> +server { + server_name www.<%= hostName %>; + return 301 $scheme://<%= hostName %>$request_uri; +} +<% } %>