├── .gitignore ├── example ├── settings.json └── mup.json ├── templates ├── sunos │ ├── run.sh │ ├── env.sh │ ├── service-manifest.xml │ └── deploy.sh └── linux │ ├── mongodb.conf │ ├── stud.init.conf │ ├── env.sh │ ├── meteor.conf │ ├── stud.conf │ └── deploy.sh ├── lib ├── taskLists │ ├── index.js │ ├── sunos.js │ └── linux.js ├── helpers.js ├── build.js ├── config.js └── actions.js ├── scripts ├── linux │ ├── setup-env.sh │ ├── install-mongodb.sh │ ├── install-stud.sh │ ├── install-phantomjs.sh │ └── install-node.sh └── sunos │ ├── setup-env.sh │ └── install-node.sh ├── package.json ├── bin └── mup ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /example/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "public": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /templates/sunos/run.sh: -------------------------------------------------------------------------------- 1 | . ./config/env.sh 2 | node ./app/main.js 3 | -------------------------------------------------------------------------------- /lib/taskLists/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(os) { 2 | return require('./' + os); 3 | }; 4 | -------------------------------------------------------------------------------- /templates/linux/mongodb.conf: -------------------------------------------------------------------------------- 1 | bind_ip = 127.0.0.1 2 | dbpath=/var/lib/mongodb/ 3 | logpath=/var/log/mongodb/mongodb.log 4 | logappend=true -------------------------------------------------------------------------------- /templates/linux/stud.init.conf: -------------------------------------------------------------------------------- 1 | #!upstart 2 | description "starting stud" 3 | author "comet" 4 | 5 | start on runlevel [2345] 6 | stop on runlevel [06] 7 | 8 | respawn 9 | limit nofile 65536 65536 10 | 11 | script 12 | stud --config=/opt/stud/stud.conf 13 | end script -------------------------------------------------------------------------------- /templates/linux/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PORT=80 3 | export MONGO_URL=mongodb://127.0.0.1/<%= appName %> 4 | export ROOT_URL=http://localhost 5 | 6 | #it is possible to override above env-vars from the user-provided values 7 | <% for(var key in env) { %> 8 | export <%- key %>=<%- ("" + env[key]).replace(/./ig, '\\$&') %> 9 | <% } %> 10 | -------------------------------------------------------------------------------- /templates/sunos/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PORT=80 3 | export MONGO_URL=mongodb://127.0.0.1/<%= appName %> 4 | export ROOT_URL=http://localhost 5 | 6 | #it is possible to override above env-vars from the user-provided values 7 | <% for(var key in env) { %> 8 | export <%- key %>=<%- ("" + env[key]).replace(/./ig, '\\$&') %> 9 | <% } %> 10 | -------------------------------------------------------------------------------- /scripts/linux/setup-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo mkdir -p /opt/<%= appName %>/ 4 | sudo mkdir -p /opt/<%= appName %>/config 5 | sudo mkdir -p /opt/<%= appName %>/tmp 6 | 7 | sudo chown ${USER} /opt/<%= appName %> -R 8 | sudo chown ${USER} /etc/init 9 | sudo chown ${USER} /etc/ 10 | 11 | sudo npm install -g forever userdown wait-for-mongo node-gyp 12 | 13 | # Creating a non-privileged user 14 | sudo useradd meteoruser || : 15 | -------------------------------------------------------------------------------- /scripts/sunos/setup-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install make and other build tool 4 | sudo pkgin -y install build-essential 5 | 6 | sudo mkdir -p /opt/<%= appName %>/ 7 | sudo mkdir -p /opt/<%= appName %>/config 8 | sudo mkdir -p /opt/<%= appName %>/tmp 9 | 10 | sudo chown ${USER} /opt/<%= appName %> -R 11 | sudo chown ${USER} /etc/init 12 | sudo chown ${USER} /etc/ 13 | 14 | sudo npm install -g userdown wait-for-mongo node-gyp 15 | 16 | # Creating a non-privileged user 17 | sudo useradd mtuser || : 18 | -------------------------------------------------------------------------------- /scripts/linux/install-mongodb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove the lock 4 | set +e 5 | sudo rm /var/lib/dpkg/lock > /dev/null 6 | sudo rm /var/cache/apt/archives/lock > /dev/null 7 | sudo dpkg --configure -a 8 | set -e 9 | 10 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 11 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list 12 | sudo apt-get update -y 13 | sudo apt-get install mongodb-org mongodb-org-server mongodb-org-shell mongodb-org-tools -y 14 | 15 | # Restart mongodb 16 | sudo stop mongod || : 17 | sudo start mongod 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mup", 3 | "version": "0.11.3", 4 | "description": "Production Quality Meteor Deployments", 5 | "dependencies": { 6 | "async": "^0.9.0", 7 | "cjson": "0.3.x", 8 | "colors": "0.6.x", 9 | "nodemiral": "^1.1.1", 10 | "rimraf": "2.x.x", 11 | "underscore": "1.7.0", 12 | "uuid": "1.4.x", 13 | "archiver": "0.14.x" 14 | }, 15 | "bin": { 16 | "mup": "./bin/mup" 17 | }, 18 | "author": "Arunoda Susiripala ", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/arunoda/meteor-up.git" 22 | }, 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /scripts/linux/install-stud.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #remove the lock 4 | set +e 5 | sudo rm /var/lib/dpkg/lock > /dev/null 6 | sudo rm /var/cache/apt/archives/lock > /dev/null 7 | sudo dpkg --configure -a 8 | set -e 9 | 10 | sudo apt-get update -y 11 | sudo apt-get -y install libev4 libev-dev gcc make libssl-dev git 12 | cd /tmp 13 | sudo rm -rf stud 14 | sudo git clone https://github.com/bumptech/stud.git stud 15 | cd stud 16 | sudo make install 17 | cd .. 18 | sudo rm -rf stud 19 | 20 | #make sure comet folder exists 21 | sudo mkdir -p /opt/stud 22 | 23 | #initial permission 24 | sudo chown -R $USER /etc/init 25 | sudo chown -R $USER /opt/stud 26 | 27 | #create non-privileged user 28 | sudo useradd stud || : -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | 3 | exports.printHelp = function() { 4 | console.error('\nValid Actions'); 5 | console.error('-------------'); 6 | console.error('init - Initialize a Meteor Up project'); 7 | console.error('setup - Setup the server'); 8 | console.error(''); 9 | console.error('deploy - Deploy app to server'); 10 | console.error('reconfig - Reconfigure the server and restart'); 11 | console.error(''); 12 | console.error('logs [-f -n] - Access logs'); 13 | console.error(''); 14 | console.error('start - Start your app instances'); 15 | console.error('stop - Stop your app instances'); 16 | console.error('restart - Restart your app instances'); 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /templates/linux/meteor.conf: -------------------------------------------------------------------------------- 1 | #!upstart 2 | description "Meteor Up - <%= appName %>" 3 | author "Arunoda Susiripala, " 4 | 5 | start on runlevel [2345] 6 | stop on runlevel [06] 7 | 8 | respawn 9 | 10 | limit nofile 65536 65536 11 | 12 | script 13 | 14 | cd /opt/<%= appName %> 15 | 16 | ##add userdown config 17 | export USERDOWN_UID=meteoruser USERDOWN_GID=meteoruser 18 | 19 | ##add custom enviromntal variables 20 | if [ -f config/env.sh ]; then 21 | . config/env.sh 22 | fi 23 | 24 | if [ -z $UPSTART_UID ]; then 25 | ##start the app using userdown 26 | forever -c userdown --minUptime 2000 --spinSleepTime 1000 app/main.js 27 | else 28 | ##start the app as UPSTART_UID 29 | exec su -s /bin/sh -c 'exec "$0" "$@"' $UPSTART_UID -- forever --minUptime 2000 --spinSleepTime 1000 app/main.js 30 | fi 31 | 32 | end script 33 | -------------------------------------------------------------------------------- /scripts/sunos/install-node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install Node.js - either nodeVersion or which works with latest Meteor release 4 | <% if (nodeVersion) { %> 5 | NODE_VERSION=<%= nodeVersion %> 6 | <% } else {%> 7 | NODE_VERSION=0.10.36 8 | <% } %> 9 | 10 | ARCH=$(python -c 'import platform; print platform.architecture()[0]') 11 | if [[ ${ARCH} == '64bit' ]]; then 12 | NODE_ARCH=x64 13 | else 14 | NODE_ARCH=x86 15 | fi 16 | 17 | sudo apt-get -y install build-essential libssl-dev git curl 18 | 19 | NODE_DIST=node-v${NODE_VERSION}-sunos-${NODE_ARCH} 20 | 21 | cd /tmp 22 | wget http://nodejs.org/dist/v${NODE_VERSION}/${NODE_DIST}.tar.gz 23 | tar xvzf ${NODE_DIST}.tar.gz 24 | sudo rm -rf /opt/nodejs 25 | sudo mv ${NODE_DIST} /opt/nodejs 26 | 27 | # set downloaded node version as the default 28 | # XXX this needs to be changed later on 29 | sudo ln -sf /opt/nodejs/bin/node /opt/local/bin/node 30 | sudo ln -sf /opt/nodejs/bin/npm /opt/local/bin/npm 31 | -------------------------------------------------------------------------------- /scripts/linux/install-phantomjs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove the lock 4 | set +e 5 | sudo rm /var/lib/dpkg/lock > /dev/null 6 | sudo rm /var/cache/apt/archives/lock > /dev/null 7 | sudo dpkg --configure -a 8 | set -e 9 | 10 | # Install PhantomJS 11 | sudo apt-get -y install libfreetype6 libfreetype6-dev fontconfig > /dev/null 12 | ARCH=`uname -m` 13 | PHANTOMJS_VERSION=1.9.8 14 | 15 | cd /usr/local/share/ 16 | sudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}.tar.bz2 > /dev/null 17 | sudo tar xjf phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}.tar.bz2 > /dev/null 18 | sudo ln -s -f /usr/local/share/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}/bin/phantomjs /usr/local/share/phantomjs 19 | sudo ln -s -f /usr/local/share/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}/bin/phantomjs /usr/local/bin/phantomjs 20 | sudo ln -s -f /usr/local/share/phantomjs-${PHANTOMJS_VERSION}-linux-${ARCH}/bin/phantomjs /usr/bin/phantomjs 21 | -------------------------------------------------------------------------------- /scripts/linux/install-node.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Remove the lock 4 | set +e 5 | sudo rm /var/lib/dpkg/lock > /dev/null 6 | sudo rm /var/cache/apt/archives/lock > /dev/null 7 | sudo dpkg --configure -a 8 | set -e 9 | 10 | # Required to update system 11 | sudo apt-get update 12 | 13 | # Install Node.js - either nodeVersion or which works with latest Meteor release 14 | <% if (nodeVersion) { %> 15 | NODE_VERSION=<%= nodeVersion %> 16 | <% } else {%> 17 | NODE_VERSION=0.10.36 18 | <% } %> 19 | 20 | ARCH=$(python -c 'import platform; print platform.architecture()[0]') 21 | if [[ ${ARCH} == '64bit' ]]; then 22 | NODE_ARCH=x64 23 | else 24 | NODE_ARCH=x86 25 | fi 26 | 27 | sudo apt-get -y install build-essential libssl-dev git curl 28 | 29 | NODE_DIST=node-v${NODE_VERSION}-linux-${NODE_ARCH} 30 | 31 | cd /tmp 32 | wget http://nodejs.org/dist/v${NODE_VERSION}/${NODE_DIST}.tar.gz 33 | tar xvzf ${NODE_DIST}.tar.gz 34 | sudo rm -rf /opt/nodejs 35 | sudo mv ${NODE_DIST} /opt/nodejs 36 | 37 | sudo ln -sf /opt/nodejs/bin/node /usr/bin/node 38 | sudo ln -sf /opt/nodejs/bin/npm /usr/bin/npm 39 | -------------------------------------------------------------------------------- /bin/mup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var cjson = require('cjson'); 5 | var Config = require('../lib/config'); 6 | var ActionsRegistry = require('../lib/actions'); 7 | var helpers = require('../lib/helpers'); 8 | require('colors'); 9 | 10 | console.log('\nMeteor Up: Production Quality Meteor Deployments'.bold.blue); 11 | console.log('------------------------------------------------\n'.bold.blue); 12 | 13 | var action = process.argv[2]; 14 | if(action == 'init') { 15 | //special setup for init 16 | ActionsRegistry.init(); 17 | } else { 18 | var cwd = path.resolve('.'); 19 | //read config and validate it 20 | var config = Config.read(); 21 | runActions(config, cwd); 22 | } 23 | 24 | 25 | function runActions(config, cwd) { 26 | var actionsRegistry = new ActionsRegistry(config, cwd); 27 | if(actionsRegistry[action]) { 28 | actionsRegistry[action](); 29 | } else { 30 | if(typeof action !== "undefined") { 31 | var errorMessage = 'No Such Action Exists: ' + action; 32 | console.error(errorMessage.bold.red); 33 | } 34 | helpers.printHelp(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MeteorHacks Pvt Ltd (Sri Lanka). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/mup.json: -------------------------------------------------------------------------------- 1 | { 2 | // Server authentication info 3 | "servers": [ 4 | { 5 | "host": "hostname", 6 | "username": "root", 7 | "password": "password" 8 | // or pem file (ssh based authentication) 9 | //"pem": "~/.ssh/id_rsa" 10 | } 11 | ], 12 | 13 | // Install MongoDB in the server, does not destroy local MongoDB on future setup 14 | "setupMongo": true, 15 | 16 | // WARNING: Node.js is required! Only skip if you already have Node.js installed on server. 17 | "setupNode": true, 18 | 19 | // WARNING: If nodeVersion omitted will setup 0.10.36 by default. Do not use v, only version number. 20 | "nodeVersion": "0.10.36", 21 | 22 | // Install PhantomJS in the server 23 | "setupPhantom": true, 24 | 25 | // Show a progress bar during the upload of the bundle to the server. 26 | // Might cause an error in some rare cases if set to true, for instance in Shippable CI 27 | "enableUploadProgressBar": true, 28 | 29 | // Application name (No spaces) 30 | "appName": "meteor", 31 | 32 | // Location of app (local directory) 33 | "app": "/path/to/the/app", 34 | 35 | // Configure environment 36 | "env": { 37 | "ROOT_URL": "http://myapp.com" 38 | }, 39 | 40 | // Meteor Up checks if the app comes online just after the deployment 41 | // before mup checks that, it will wait for no. of seconds configured below 42 | "deployCheckWaitTime": 15 43 | } 44 | -------------------------------------------------------------------------------- /templates/sunos/service-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /lib/build.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn; 2 | var archiver = require('archiver'); 3 | var fs = require('fs'); 4 | var pathResolve = require('path').resolve; 5 | var _ = require('underscore'); 6 | 7 | function buildApp(appPath, meteorBinary, buildLocaltion, callback) { 8 | buildMeteorApp(appPath, meteorBinary, buildLocaltion, function(code) { 9 | if(code == 0) { 10 | archiveIt(buildLocaltion, callback); 11 | } else { 12 | console.log("\n=> Build Error. Check the logs printed above."); 13 | callback(new Error("build-error")); 14 | } 15 | }); 16 | } 17 | 18 | function buildMeteorApp(appPath, meteorBinary, buildLocaltion, callback) { 19 | var executable = meteorBinary; 20 | var args = [ 21 | "build", "--directory", buildLocaltion, 22 | "--server", "http://localhost:3000" 23 | ]; 24 | 25 | var isWin = /^win/.test(process.platform); 26 | if(isWin) { 27 | // Sometimes cmd.exe not available in the path 28 | // See: http://goo.gl/ADmzoD 29 | executable = process.env.comspec || "cmd.exe"; 30 | args = ["/c", "meteor"].concat(args); 31 | } 32 | 33 | var options = {cwd: appPath}; 34 | var meteor = spawn(executable, args, options); 35 | var stdout = ""; 36 | var stderr = ""; 37 | 38 | meteor.stdout.pipe(process.stdout, {end: false}); 39 | meteor.stderr.pipe(process.stderr, {end: false}); 40 | 41 | meteor.on('close', callback); 42 | } 43 | 44 | function archiveIt(buildLocaltion, callback) { 45 | callback = _.once(callback); 46 | var bundlePath = pathResolve(buildLocaltion, 'bundle.tar.gz'); 47 | var sourceDir = pathResolve(buildLocaltion, 'bundle'); 48 | 49 | var output = fs.createWriteStream(bundlePath); 50 | var archive = archiver('tar', { 51 | gzip: true, 52 | gzipOptions: { 53 | level: 6 54 | } 55 | }); 56 | 57 | archive.pipe(output); 58 | output.once('close', callback); 59 | 60 | archive.once('error', function(err) { 61 | console.log("=> Archiving failed:", err.message); 62 | callback(err); 63 | }); 64 | 65 | archive.directory(sourceDir, 'bundle').finalize(); 66 | } 67 | 68 | module.exports = buildApp; 69 | -------------------------------------------------------------------------------- /templates/sunos/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # utilities 4 | gyp_rebuild_inside_node_modules () { 5 | for npmModule in ./*; do 6 | cd $npmModule 7 | if [ -f binding.gyp ]; then 8 | echo "=> re-installing binary npm module '${npmModule:2}' of package '${package:2}'" 9 | sudo node-gyp rebuild 10 | fi 11 | 12 | # recursively rebuild npm modules inside node_modules 13 | if [ -d ./node_modules ]; then 14 | cd ./node_modules 15 | gyp_rebuild_inside_node_modules 16 | cd ../ 17 | fi 18 | cd .. 19 | done 20 | } 21 | 22 | rebuild_binary_npm_modules () { 23 | for package in ./*; do 24 | if [ -d $package ]; then 25 | cd $package/node_modules 26 | gyp_rebuild_inside_node_modules 27 | cd ../../ 28 | fi 29 | done 30 | } 31 | 32 | revert_app (){ 33 | if [[ -d old_app ]]; then 34 | sudo rm -rf app 35 | sudo mv old_app app 36 | sudo stop <%= appName %> || : 37 | sudo start <%= appName %> || : 38 | 39 | echo "Latest deployment failed! Reverted back to the previous version." 1>&2 40 | exit 1 41 | else 42 | echo "App did not pick up! Please check app logs." 1>&2 43 | exit 1 44 | fi 45 | } 46 | 47 | # logic 48 | set -e 49 | 50 | TMP_DIR=/opt/<%= appName %>/tmp 51 | BUNDLE_DIR=${TMP_DIR}/bundle 52 | 53 | cd ${TMP_DIR} 54 | sudo rm -rf bundle 55 | sudo tar xvzf bundle.tar.gz > /dev/null 56 | 57 | # rebuilding fibers 58 | cd ${BUNDLE_DIR}/programs/server 59 | 60 | if [ -f package.json ]; then 61 | # support for 0.9 62 | sudo npm install 63 | cd npm 64 | rebuild_binary_npm_modules 65 | cd ../ 66 | else 67 | # support for older versions 68 | sudo npm install fibers 69 | sudo npm install bcrypt 70 | fi 71 | 72 | cd /opt/<%= appName %>/ 73 | 74 | # remove old app, if it exists 75 | if [ -d old_app ]; then 76 | sudo rm -rf old_app 77 | fi 78 | 79 | ## backup current version 80 | if [[ -d app ]]; then 81 | sudo mv app old_app 82 | fi 83 | 84 | sudo mv tmp/bundle app 85 | 86 | #wait and check 87 | echo "Waiting for MongoDB to initialize. (5 minutes)" 88 | . /opt/<%= appName %>/config/env.sh 89 | wait-for-mongo ${MONGO_URL} 300000 90 | 91 | # restart app 92 | sudo svcadm disable <%= appName %> || : 93 | sudo svcadm enable <%= appName %> || : 94 | 95 | echo "Waiting for <%= deployCheckWaitTime %> seconds while app is booting up" 96 | sleep <%= deployCheckWaitTime %> 97 | 98 | echo "Checking is app booted or not?" 99 | curl localhost:${PORT} || revert_app 100 | 101 | ## chown to support dumping heapdump and etc 102 | # sudo chown -R meteoruser app 103 | -------------------------------------------------------------------------------- /templates/linux/stud.conf: -------------------------------------------------------------------------------- 1 | # 2 | # stud(8), The Scalable TLS Unwrapping Daemon's configuration 3 | # 4 | 5 | # NOTE: all config file parameters can be overriden 6 | # from command line! 7 | 8 | # Listening address. REQUIRED. 9 | # 10 | # type: string 11 | # syntax: [HOST]:PORT 12 | frontend = "[*]:443" 13 | 14 | # Upstream server address. REQUIRED. 15 | # 16 | # type: string 17 | # syntax: [HOST]:PORT. 18 | backend = "<%= backend %>" 19 | 20 | # SSL x509 certificate file. REQUIRED. 21 | # List multiple certs to use SNI. Certs are used in the order they 22 | # are listed; the last cert listed will be used if none of the others match 23 | # 24 | # type: string 25 | 26 | # since docker can see config folder as /config we need to set ssl accordingly. 27 | # actual location will be /opt/comet/stud.conf 28 | pem-file = "/opt/stud/ssl.pem" 29 | 30 | # SSL protocol. 31 | # 32 | # tls = on 33 | # ssl = off 34 | 35 | # List of allowed SSL ciphers. 36 | # 37 | # Run openssl ciphers for list of available ciphers. 38 | # type: string 39 | ciphers = "" 40 | 41 | # Enforce server cipher list order 42 | # 43 | # type: boolean 44 | prefer-server-ciphers = off 45 | 46 | # Use specified SSL engine 47 | # 48 | # type: string 49 | ssl-engine = "" 50 | 51 | # Number of worker processes 52 | # 53 | # type: integer 54 | workers = 1 55 | 56 | # Listen backlog size 57 | # 58 | # type: integer 59 | backlog = 100 60 | 61 | # TCP socket keepalive interval in seconds 62 | # 63 | # type: integer 64 | keepalive = 3600 65 | 66 | # Chroot directory 67 | # 68 | # type: string 69 | chroot = "" 70 | 71 | # Set uid after binding a socket 72 | # 73 | # type: string 74 | user = "stud" 75 | 76 | # Set gid after binding a socket 77 | # 78 | # type: string 79 | group = "stud" 80 | 81 | # Quiet execution, report only error messages 82 | # 83 | # type: boolean 84 | quiet = on 85 | 86 | # Use syslog for logging 87 | # 88 | # type: boolean 89 | syslog = off 90 | 91 | # Syslog facility to use 92 | # 93 | # type: string 94 | syslog-facility = "daemon" 95 | 96 | # Run as daemon 97 | # 98 | # type: boolean 99 | daemon = off 100 | 101 | # Report client address by writing IP before sending data 102 | # 103 | # NOTE: This option is mutually exclusive with option write-proxy and proxy-proxy. 104 | # 105 | # type: boolean 106 | write-ip = off 107 | 108 | # Report client address using SENDPROXY protocol, see 109 | # http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt 110 | # for details. 111 | # 112 | # NOTE: This option is mutually exclusive with option write-ip and proxy-proxy. 113 | # 114 | # type: boolean 115 | write-proxy = off 116 | 117 | # Proxy an existing SENDPROXY protocol header through this request. 118 | # 119 | # NOTE: This option is mutually exclusive with option write-ip and write-proxy. 120 | # 121 | # type: boolean 122 | proxy-proxy = off 123 | 124 | # EOF -------------------------------------------------------------------------------- /templates/linux/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # utilities 4 | gyp_rebuild_inside_node_modules () { 5 | for npmModule in ./*; do 6 | cd $npmModule 7 | 8 | isBinaryModule="no" 9 | # recursively rebuild npm modules inside node_modules 10 | check_for_binary_modules () { 11 | if [ -f binding.gyp ]; then 12 | isBinaryModule="yes" 13 | fi 14 | 15 | if [ $isBinaryModule != "yes" ]; then 16 | if [ -d ./node_modules ]; then 17 | cd ./node_modules 18 | for module in ./*; do 19 | cd $module 20 | check_for_binary_modules 21 | cd .. 22 | done 23 | cd ../ 24 | fi 25 | fi 26 | } 27 | 28 | check_for_binary_modules 29 | 30 | if [ $isBinaryModule = "yes" ]; then 31 | echo " > $npmModule: npm install due to binary npm modules" 32 | rm -rf node_modules 33 | if [ -f binding.gyp ]; then 34 | sudo npm install 35 | sudo node-gyp rebuild || : 36 | else 37 | sudo npm install 38 | fi 39 | fi 40 | 41 | cd .. 42 | done 43 | } 44 | 45 | rebuild_binary_npm_modules () { 46 | for package in ./*; do 47 | if [ -d $package/node_modules ]; then 48 | cd $package/node_modules 49 | gyp_rebuild_inside_node_modules 50 | cd ../../ 51 | elif [ -d $package/main/node_module ]; then 52 | cd $package/node_modules 53 | gyp_rebuild_inside_node_modules 54 | cd ../../../ 55 | fi 56 | done 57 | } 58 | 59 | revert_app (){ 60 | if [[ -d old_app ]]; then 61 | sudo rm -rf app 62 | sudo mv old_app app 63 | sudo stop <%= appName %> || : 64 | sudo start <%= appName %> || : 65 | 66 | echo "Latest deployment failed! Reverted back to the previous version." 1>&2 67 | exit 1 68 | else 69 | echo "App did not pick up! Please check app logs." 1>&2 70 | exit 1 71 | fi 72 | } 73 | 74 | 75 | # logic 76 | set -e 77 | 78 | TMP_DIR=/opt/<%= appName %>/tmp 79 | BUNDLE_DIR=${TMP_DIR}/bundle 80 | 81 | cd ${TMP_DIR} 82 | sudo rm -rf bundle 83 | sudo tar xvzf bundle.tar.gz > /dev/null 84 | sudo chmod -R +x * 85 | sudo chown -R ${USER} ${BUNDLE_DIR} 86 | 87 | # rebuilding fibers 88 | cd ${BUNDLE_DIR}/programs/server 89 | 90 | if [ -d ./npm ]; then 91 | cd npm 92 | rebuild_binary_npm_modules 93 | cd ../ 94 | fi 95 | 96 | if [ -d ./node_modules ]; then 97 | cd ./node_modules 98 | gyp_rebuild_inside_node_modules 99 | cd ../ 100 | fi 101 | 102 | if [ -f package.json ]; then 103 | # support for 0.9 104 | sudo npm install 105 | else 106 | # support for older versions 107 | sudo npm install fibers 108 | sudo npm install bcrypt 109 | fi 110 | 111 | cd /opt/<%= appName %>/ 112 | 113 | # remove old app, if it exists 114 | if [ -d old_app ]; then 115 | sudo rm -rf old_app 116 | fi 117 | 118 | ## backup current version 119 | if [[ -d app ]]; then 120 | sudo mv app old_app 121 | fi 122 | 123 | sudo mv tmp/bundle app 124 | 125 | #wait and check 126 | echo "Waiting for MongoDB to initialize. (5 minutes)" 127 | . /opt/<%= appName %>/config/env.sh 128 | wait-for-mongo ${MONGO_URL} 300000 129 | 130 | # restart app 131 | sudo stop <%= appName %> || : 132 | sudo start <%= appName %> || : 133 | 134 | echo "Waiting for <%= deployCheckWaitTime %> seconds while app is booting up" 135 | sleep <%= deployCheckWaitTime %> 136 | 137 | echo "Checking is app booted or not?" 138 | curl localhost:${PORT} || revert_app 139 | 140 | # chown to support dumping heapdump and etc 141 | sudo chown -R meteoruser app 142 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var cjson = require('cjson'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var helpers = require('./helpers'); 5 | var format = require('util').format; 6 | 7 | require('colors'); 8 | 9 | exports.read = function() { 10 | var mupJsonPath = path.resolve('mup.json'); 11 | if(fs.existsSync(mupJsonPath)) { 12 | var mupJson = cjson.load(mupJsonPath); 13 | 14 | //initialize options 15 | mupJson.env = mupJson.env || {}; 16 | 17 | if(typeof mupJson.setupNode === "undefined") { 18 | mupJson.setupNode = true; 19 | } 20 | if(typeof mupJson.setupPhantom === "undefined") { 21 | mupJson.setupPhantom = true; 22 | } 23 | mupJson.meteorBinary = (mupJson.meteorBinary) ? getCanonicalPath(mupJson.meteorBinary) : 'meteor'; 24 | if(typeof mupJson.appName === "undefined") { 25 | mupJson.appName = "meteor"; 26 | } 27 | if(typeof mupJson.enableUploadProgressBar === "undefined") { 28 | mupJson.enableUploadProgressBar = true; 29 | } 30 | 31 | //validating servers 32 | if(!mupJson.servers || mupJson.servers.length == 0) { 33 | mupErrorLog('Server information does not exist'); 34 | } else { 35 | mupJson.servers.forEach(function(server) { 36 | var sshAgentExists = false; 37 | var sshAgent = process.env.SSH_AUTH_SOCK; 38 | if(sshAgent) { 39 | sshAgentExists = fs.existsSync(sshAgent); 40 | server.sshOptions = server.sshOptions || {}; 41 | server.sshOptions.agent = sshAgent; 42 | } 43 | 44 | if(!server.host) { 45 | mupErrorLog('Server host does not exist'); 46 | } else if(!server.username) { 47 | mupErrorLog('Server username does not exist'); 48 | } else if(!server.password && !server.pem && !sshAgentExists) { 49 | mupErrorLog('Server password, pem or a ssh agent does not exist'); 50 | } else if(!mupJson.app) { 51 | mupErrorLog('Path to app does not exist'); 52 | } 53 | 54 | server.os = server.os || "linux"; 55 | 56 | if(server.pem) { 57 | server.pem = rewriteHome(server.pem); 58 | } 59 | 60 | server.env = server.env || {}; 61 | var defaultEndpointUrl = 62 | format("http://%s:%s", server.host, mupJson.env['PORT'] || 80); 63 | server.env['CLUSTER_ENDPOINT_URL'] = 64 | server.env['CLUSTER_ENDPOINT_URL'] || defaultEndpointUrl; 65 | }); 66 | } 67 | 68 | //rewrite ~ with $HOME 69 | mupJson.app = rewriteHome(mupJson.app); 70 | 71 | if(mupJson.ssl) { 72 | mupJson.ssl.backendPort = mupJson.ssl.backendPort || 80; 73 | mupJson.ssl.pem = path.resolve(rewriteHome(mupJson.ssl.pem)); 74 | if(!fs.existsSync(mupJson.ssl.pem)) { 75 | mupErrorLog('SSL pem file does not exist'); 76 | } 77 | } 78 | 79 | return mupJson; 80 | } else { 81 | console.error('mup.json file does not exist!'.red.bold); 82 | helpers.printHelp(); 83 | process.exit(1); 84 | } 85 | }; 86 | 87 | function rewriteHome(location) { 88 | if(/^win/.test(process.platform)) { 89 | return location.replace('~', process.env.USERPROFILE); 90 | } else { 91 | return location.replace('~', process.env.HOME); 92 | } 93 | } 94 | 95 | function mupErrorLog(message) { 96 | var errorMessage = 'Invalid mup.json file: ' + message; 97 | console.error(errorMessage.red.bold); 98 | process.exit(1); 99 | } 100 | 101 | function getCanonicalPath(location) { 102 | var localDir = path.resolve(__dirname, location); 103 | if(fs.existsSync(localDir)) { 104 | return localDir; 105 | } else { 106 | return path.resolve(rewriteHome(location)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/taskLists/sunos.js: -------------------------------------------------------------------------------- 1 | var nodemiral = require('nodemiral'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var SCRIPT_DIR = path.resolve(__dirname, '../../scripts/sunos'); 6 | var TEMPLATES_DIR = path.resolve(__dirname, '../../templates/sunos'); 7 | 8 | exports.setup = function(installMongo, setupNode, nodeVersion, setupPhantom, appName) { 9 | var taskList = nodemiral.taskList('Setup (sunos)'); 10 | 11 | // Installation 12 | if(setupNode) { 13 | taskList.executeScript('Installing Node.js', { 14 | script: path.resolve(SCRIPT_DIR, 'install-node.sh'), 15 | vars: { 16 | nodeVersion: nodeVersion 17 | } 18 | }); 19 | } 20 | 21 | taskList.executeScript('Setting up Environment', { 22 | script: path.resolve(SCRIPT_DIR, 'setup-env.sh'), 23 | vars: { 24 | appName: appName 25 | } 26 | }); 27 | 28 | taskList.copy('Setting up Running Script', { 29 | src: path.resolve(TEMPLATES_DIR, 'run.sh'), 30 | dest: '/opt/' + appName + '/run.sh', 31 | vars: { 32 | appName: appName 33 | } 34 | }); 35 | 36 | var serviceManifestDest = '/opt/' + appName + '/config/service-manifest.xml'; 37 | taskList.copy('Copying SMF Manifest', { 38 | src: path.resolve(TEMPLATES_DIR, 'service-manifest.xml'), 39 | dest: serviceManifestDest, 40 | vars: { 41 | appName: appName 42 | } 43 | }); 44 | 45 | taskList.execute('Configuring SMF Manifest', { 46 | command: 'sudo svccfg import ' + serviceManifestDest 47 | }); 48 | 49 | return taskList; 50 | }; 51 | 52 | exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName) { 53 | var taskList = nodemiral.taskList("Deploy app '" + appName + "' (sunos)"); 54 | 55 | taskList.copy('Uploading bundle', { 56 | src: bundlePath, 57 | dest: '/opt/' + appName + '/tmp/bundle.tar.gz' 58 | }); 59 | 60 | reconfig(taskList, appName, env); 61 | 62 | // deploying 63 | taskList.executeScript('Invoking deployment process', { 64 | script: path.resolve(TEMPLATES_DIR, 'deploy.sh'), 65 | vars: { 66 | deployCheckWaitTime: deployCheckWaitTime || 10, 67 | appName: appName 68 | } 69 | }); 70 | 71 | return taskList; 72 | }; 73 | 74 | exports.reconfig = function(env, appName) { 75 | var taskList = nodemiral.taskList("Updating configurations (sunos)"); 76 | 77 | reconfig(taskList, appName, env); 78 | 79 | //deploying 80 | taskList.execute('Restarting app', { 81 | command: '(sudo svcadm disable ' + appName + ' || :) && (sudo svcadm enable ' + appName + ')' 82 | }); 83 | 84 | return taskList; 85 | }; 86 | 87 | exports.restart = function(appName) { 88 | var taskList = nodemiral.taskList("Restarting Application (sunos)"); 89 | 90 | //restarting 91 | taskList.execute('Restarting app', { 92 | command: '(sudo svcadm disable ' + appName + ' || :) && (sudo svcadm enable ' + appName + ')' 93 | }); 94 | 95 | return taskList; 96 | }; 97 | 98 | exports.stop = function(appName) { 99 | var taskList = nodemiral.taskList("Stopping Application (sunos)"); 100 | 101 | //stopping 102 | taskList.execute('Stopping app', { 103 | command: '(sudo svcadm disable ' + appName + ')' 104 | }); 105 | 106 | return taskList; 107 | }; 108 | 109 | exports.start = function(appName) { 110 | var taskList = nodemiral.taskList("Starting Application (sunos)"); 111 | 112 | reconfig(taskList, appName, env); 113 | 114 | //starting 115 | taskList.execute('Starting app', { 116 | command: '(sudo svcadm enable ' + appName + ')' 117 | }); 118 | 119 | return taskList; 120 | }; 121 | 122 | function reconfig(taskList, appName, env) { 123 | taskList.copy('Setting up Environment Variables', { 124 | src: path.resolve(TEMPLATES_DIR, 'env.sh'), 125 | dest: '/opt/' + appName + '/config/env.sh', 126 | vars: { 127 | env: env || {}, 128 | appName: appName 129 | } 130 | }); 131 | } 132 | -------------------------------------------------------------------------------- /lib/taskLists/linux.js: -------------------------------------------------------------------------------- 1 | var nodemiral = require('nodemiral'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var util = require('util'); 5 | 6 | var SCRIPT_DIR = path.resolve(__dirname, '../../scripts/linux'); 7 | var TEMPLATES_DIR = path.resolve(__dirname, '../../templates/linux'); 8 | 9 | exports.setup = function(config) { 10 | var taskList = nodemiral.taskList('Setup (linux)'); 11 | 12 | // Installation 13 | if(config.setupNode) { 14 | taskList.executeScript('Installing Node.js', { 15 | script: path.resolve(SCRIPT_DIR, 'install-node.sh'), 16 | vars: { 17 | nodeVersion: config.nodeVersion 18 | } 19 | }); 20 | } 21 | 22 | if(config.setupPhantom) { 23 | taskList.executeScript('Installing PhantomJS', { 24 | script: path.resolve(SCRIPT_DIR, 'install-phantomjs.sh') 25 | }); 26 | } 27 | 28 | taskList.executeScript('Setting up Environment', { 29 | script: path.resolve(SCRIPT_DIR, 'setup-env.sh'), 30 | vars: { 31 | appName: config.appName 32 | } 33 | }); 34 | 35 | if(config.setupMongo) { 36 | taskList.copy('Copying MongoDB configuration', { 37 | src: path.resolve(TEMPLATES_DIR, 'mongodb.conf'), 38 | dest: '/etc/mongodb.conf' 39 | }); 40 | 41 | taskList.executeScript('Installing MongoDB', { 42 | script: path.resolve(SCRIPT_DIR, 'install-mongodb.sh') 43 | }); 44 | } 45 | 46 | if(config.ssl) { 47 | installStud(taskList); 48 | configureStud(taskList, config.ssl.pem, config.ssl.backendPort); 49 | } 50 | 51 | //Configurations 52 | taskList.copy('Configuring upstart', { 53 | src: path.resolve(TEMPLATES_DIR, 'meteor.conf'), 54 | dest: '/etc/init/' + config.appName + '.conf', 55 | vars: { 56 | appName: config.appName 57 | } 58 | }); 59 | 60 | return taskList; 61 | }; 62 | 63 | exports.deploy = function(bundlePath, env, deployCheckWaitTime, appName, enableUploadProgressBar) { 64 | var taskList = nodemiral.taskList("Deploy app '" + appName + "' (linux)"); 65 | 66 | taskList.copy('Uploading bundle', { 67 | src: bundlePath, 68 | dest: '/opt/' + appName + '/tmp/bundle.tar.gz', 69 | progressBar: enableUploadProgressBar 70 | }); 71 | 72 | taskList.copy('Setting up Environment Variables', { 73 | src: path.resolve(TEMPLATES_DIR, 'env.sh'), 74 | dest: '/opt/' + appName + '/config/env.sh', 75 | vars: { 76 | env: env || {}, 77 | appName: appName 78 | } 79 | }); 80 | 81 | // deploying 82 | taskList.executeScript('Invoking deployment process', { 83 | script: path.resolve(TEMPLATES_DIR, 'deploy.sh'), 84 | vars: { 85 | deployCheckWaitTime: deployCheckWaitTime || 10, 86 | appName: appName 87 | } 88 | }); 89 | 90 | return taskList; 91 | }; 92 | 93 | exports.reconfig = function(env, appName) { 94 | var taskList = nodemiral.taskList("Updating configurations (linux)"); 95 | 96 | taskList.copy('Setting up Environment Variables', { 97 | src: path.resolve(TEMPLATES_DIR, 'env.sh'), 98 | dest: '/opt/' + appName + '/config/env.sh', 99 | vars: { 100 | env: env || {}, 101 | appName: appName 102 | } 103 | }); 104 | 105 | //restarting 106 | taskList.execute('Restarting app', { 107 | command: '(sudo stop ' + appName + ' || :) && (sudo start ' + appName + ')' 108 | }); 109 | 110 | return taskList; 111 | }; 112 | 113 | exports.restart = function(appName) { 114 | var taskList = nodemiral.taskList("Restarting Application (linux)"); 115 | 116 | //restarting 117 | taskList.execute('Restarting app', { 118 | command: '(sudo stop ' + appName + ' || :) && (sudo start ' + appName + ')' 119 | }); 120 | 121 | return taskList; 122 | }; 123 | 124 | exports.stop = function(appName) { 125 | var taskList = nodemiral.taskList("Stopping Application (linux)"); 126 | 127 | //stopping 128 | taskList.execute('Stopping app', { 129 | command: '(sudo stop ' + appName + ')' 130 | }); 131 | 132 | return taskList; 133 | }; 134 | 135 | exports.start = function(appName) { 136 | var taskList = nodemiral.taskList("Starting Application (linux)"); 137 | 138 | //starting 139 | taskList.execute('Starting app', { 140 | command: '(sudo start ' + appName + ')' 141 | }); 142 | 143 | return taskList; 144 | }; 145 | 146 | function installStud(taskList) { 147 | taskList.executeScript('Installing Stud', { 148 | script: path.resolve(SCRIPT_DIR, 'install-stud.sh') 149 | }); 150 | } 151 | 152 | function configureStud(taskList, pemFilePath, port) { 153 | var backend = {host: '127.0.0.1', port: port}; 154 | 155 | taskList.copy('Configuring Stud for Upstart', { 156 | src: path.resolve(TEMPLATES_DIR, 'stud.init.conf'), 157 | dest: '/etc/init/stud.conf' 158 | }); 159 | 160 | taskList.copy('Configuring SSL', { 161 | src: pemFilePath, 162 | dest: '/opt/stud/ssl.pem' 163 | }); 164 | 165 | 166 | taskList.copy('Configuring Stud', { 167 | src: path.resolve(TEMPLATES_DIR, 'stud.conf'), 168 | dest: '/opt/stud/stud.conf', 169 | vars: { 170 | backend: util.format('[%s]:%d', backend.host, backend.port) 171 | } 172 | }); 173 | 174 | taskList.execute('Verifying SSL Configurations (ssl.pem)', { 175 | command: 'stud --test --config=/opt/stud/stud.conf' 176 | }); 177 | 178 | //restart stud 179 | taskList.execute('Starting Stud', { 180 | command: '(sudo stop stud || :) && (sudo start stud || :)' 181 | }); 182 | } 183 | -------------------------------------------------------------------------------- /lib/actions.js: -------------------------------------------------------------------------------- 1 | var nodemiral = require('nodemiral'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var rimraf = require('rimraf'); 5 | var exec = require('child_process').exec; 6 | var spawn = require('child_process').spawn; 7 | var uuid = require('uuid'); 8 | var format = require('util').format; 9 | var extend = require('util')._extend; 10 | var _ = require('underscore'); 11 | var async = require('async'); 12 | var buildApp = require('./build.js'); 13 | var os = require('os'); 14 | require('colors'); 15 | 16 | module.exports = Actions; 17 | 18 | function Actions(config, cwd) { 19 | this.cwd = cwd; 20 | this.config = config; 21 | this.sessionsMap = this._createSessionsMap(config); 22 | 23 | //get settings.json into env 24 | var setttingsJsonPath = path.resolve(this.cwd, 'settings.json'); 25 | if(fs.existsSync(setttingsJsonPath)) { 26 | this.config.env['METEOR_SETTINGS'] = JSON.stringify(require(setttingsJsonPath)); 27 | } 28 | } 29 | 30 | Actions.prototype._createSessionsMap = function(config) { 31 | var sessionsMap = {}; 32 | 33 | config.servers.forEach(function(server) { 34 | var host = server.host; 35 | var auth = {username: server.username}; 36 | 37 | if(server.pem) { 38 | auth.pem = fs.readFileSync(path.resolve(server.pem), 'utf8'); 39 | } else { 40 | auth.password = server.password; 41 | } 42 | 43 | var nodemiralOptions = { 44 | ssh: server.sshOptions, 45 | keepAlive: true 46 | }; 47 | 48 | if(!sessionsMap[server.os]) { 49 | sessionsMap[server.os] = { 50 | sessions: [], 51 | taskListsBuilder:require('./taskLists')(server.os) 52 | }; 53 | } 54 | 55 | var session = nodemiral.session(host, auth, nodemiralOptions); 56 | session._serverConfig = server; 57 | sessionsMap[server.os].sessions.push(session); 58 | }); 59 | 60 | return sessionsMap; 61 | }; 62 | 63 | var kadiraRegex = /^meteorhacks:kadira/m; 64 | Actions.prototype._showKadiraLink = function() { 65 | var versionsFile = path.join(this.config.app, '.meteor/versions'); 66 | if(fs.existsSync(versionsFile)) { 67 | var packages = fs.readFileSync(versionsFile, 'utf-8'); 68 | var hasKadira = kadiraRegex.test(packages); 69 | if(!hasKadira) { 70 | console.log( 71 | "“ Checkout " + "Kadira".bold + "!"+ 72 | "\n It's the best way to monitor performance of your app."+ 73 | "\n Visit: " + "https://kadira.io/mup".underline + " ”\n" 74 | ); 75 | } 76 | } 77 | }; 78 | 79 | Actions.prototype._executePararell = function(actionName, args) { 80 | var self = this; 81 | var sessionInfoList = _.values(self.sessionsMap); 82 | async.map( 83 | sessionInfoList, 84 | function(sessionsInfo, callback) { 85 | var taskList = sessionsInfo.taskListsBuilder[actionName] 86 | .apply(sessionsInfo.taskListsBuilder, args); 87 | taskList.run(sessionsInfo.sessions, function(summaryMap) { 88 | callback(null, summaryMap); 89 | }); 90 | }, 91 | whenAfterCompleted 92 | ); 93 | }; 94 | 95 | Actions.prototype.setup = function() { 96 | this._showKadiraLink(); 97 | this._executePararell("setup", [this.config]); 98 | }; 99 | 100 | Actions.prototype.deploy = function() { 101 | var self = this; 102 | self._showKadiraLink(); 103 | 104 | var buildLocation = path.resolve(os.tmpdir(), uuid.v4()); 105 | var bundlePath = path.resolve(buildLocation, 'bundle.tar.gz'); 106 | 107 | // spawn inherits env vars from process.env 108 | // so we can simply set them like this 109 | process.env.BUILD_LOCATION = buildLocation; 110 | 111 | var deployCheckWaitTime = this.config.deployCheckWaitTime; 112 | var appName = this.config.appName; 113 | var appPath = this.config.app; 114 | var enableUploadProgressBar = this.config.enableUploadProgressBar; 115 | var meteorBinary = this.config.meteorBinary; 116 | 117 | console.log('Building Started: ' + this.config.app); 118 | buildApp(appPath, meteorBinary, buildLocation, function(err) { 119 | if(err) { 120 | process.exit(1); 121 | } else { 122 | var sessionsData = []; 123 | _.forEach(self.sessionsMap, function (sessionsInfo) { 124 | var taskListsBuilder = sessionsInfo.taskListsBuilder; 125 | _.forEach(sessionsInfo.sessions, function (session) { 126 | sessionsData.push({ 127 | taskListsBuilder: taskListsBuilder, 128 | session: session 129 | }); 130 | }); 131 | }); 132 | 133 | async.mapSeries( 134 | sessionsData, 135 | function (sessionData, callback) { 136 | var session = sessionData.session; 137 | var taskListsBuilder = sessionData.taskListsBuilder; 138 | var env = _.extend({}, self.config.env, session._serverConfig.env); 139 | var taskList = taskListsBuilder.deploy( 140 | bundlePath, env, 141 | deployCheckWaitTime, appName, enableUploadProgressBar); 142 | taskList.run(session, function (summaryMap) { 143 | callback(null, summaryMap); 144 | }); 145 | }, 146 | whenAfterDeployed(buildLocation) 147 | ); 148 | } 149 | }); 150 | }; 151 | 152 | Actions.prototype.reconfig = function() { 153 | var self = this; 154 | var sessionInfoList = []; 155 | for(var os in self.sessionsMap) { 156 | var sessionsInfo = self.sessionsMap[os]; 157 | sessionsInfo.sessions.forEach(function(session) { 158 | var env = _.extend({}, self.config.env, session._serverConfig.env); 159 | var taskList = sessionsInfo.taskListsBuilder.reconfig( 160 | env, self.config.appName); 161 | sessionInfoList.push({ 162 | taskList: taskList, 163 | session: session 164 | }); 165 | }); 166 | } 167 | 168 | async.mapSeries( 169 | sessionInfoList, 170 | function(sessionInfo, callback) { 171 | sessionInfo.taskList.run(sessionInfo.session, function(summaryMap) { 172 | callback(null, summaryMap); 173 | }); 174 | }, 175 | whenAfterCompleted 176 | ); 177 | }; 178 | 179 | Actions.prototype.restart = function() { 180 | this._executePararell("restart", [this.config.appName]); 181 | }; 182 | 183 | Actions.prototype.stop = function() { 184 | this._executePararell("stop", [this.config.appName]); 185 | }; 186 | 187 | Actions.prototype.start = function() { 188 | this._executePararell("start", [this.config.appName]); 189 | }; 190 | 191 | Actions.prototype.logs = function() { 192 | var self = this; 193 | var tailOptions = process.argv.slice(3).join(" "); 194 | 195 | for(var os in self.sessionsMap) { 196 | var sessionsInfo = self.sessionsMap[os]; 197 | sessionsInfo.sessions.forEach(function(session) { 198 | var hostPrefix = '[' + session._host + '] '; 199 | var options = { 200 | onStdout: function(data) { 201 | process.stdout.write(hostPrefix + data.toString()); 202 | }, 203 | onStderr: function(data) { 204 | process.stderr.write(hostPrefix + data.toString()); 205 | } 206 | }; 207 | 208 | if(os == 'linux') { 209 | var command = 'sudo tail ' + tailOptions + ' /var/log/upstart/' + self.config.appName + '.log'; 210 | } else if(os == 'sunos') { 211 | var command = 'sudo tail ' + tailOptions + 212 | ' /var/svc/log/site-' + self.config.appName + '\\:default.log'; 213 | } 214 | session.execute(command, options); 215 | }); 216 | } 217 | 218 | }; 219 | 220 | Actions.init = function() { 221 | var destMupJson = path.resolve('mup.json'); 222 | var destSettingsJson = path.resolve('settings.json'); 223 | 224 | if(fs.existsSync(destMupJson) || fs.existsSync(destSettingsJson)) { 225 | console.error('A Project Already Exists'.bold.red); 226 | process.exit(1); 227 | } 228 | 229 | var exampleMupJson = path.resolve(__dirname, '../example/mup.json'); 230 | var exampleSettingsJson = path.resolve(__dirname, '../example/settings.json'); 231 | 232 | copyFile(exampleMupJson, destMupJson); 233 | copyFile(exampleSettingsJson, destSettingsJson); 234 | 235 | console.log('Empty Project Initialized!'.bold.green); 236 | 237 | function copyFile(src, dest) { 238 | var content = fs.readFileSync(src, 'utf8'); 239 | fs.writeFileSync(dest, content); 240 | } 241 | }; 242 | 243 | function storeLastNChars(vars, field, limit, color) { 244 | return function(data) { 245 | vars[field] += data.toString(); 246 | if(vars[field].length > 1000) { 247 | vars[field] = vars[field].substring(vars[field].length - 1000); 248 | } 249 | }; 250 | } 251 | 252 | function whenAfterDeployed(buildLocation) { 253 | return function(error, summaryMaps) { 254 | rimraf.sync(buildLocation); 255 | whenAfterCompleted(error, summaryMaps); 256 | }; 257 | } 258 | 259 | function whenAfterCompleted(error, summaryMaps) { 260 | var errorCode = error || haveSummaryMapsErrors(summaryMaps) ? 1 : 0; 261 | process.exit(errorCode); 262 | } 263 | 264 | function haveSummaryMapsErrors(summaryMaps) { 265 | return _.some(summaryMaps, hasSummaryMapErrors); 266 | } 267 | 268 | function hasSummaryMapErrors(summaryMap) { 269 | return _.some(summaryMap, function (summary) { 270 | return summary.error; 271 | }); 272 | } 273 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meteor Up 2 | 3 | > This version is no longer maintaining.
4 | > [Mupx](https://github.com/arunoda/meteor-up/tree/mupx) is the stable version.
5 | > New development is moved to here: https://github.com/kadirahq/meteor-up. 6 | 7 | #### Production Quality Meteor Deployments 8 | 9 | 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) 10 | 11 | You can use install and use Meteor Up from Linux, Mac and Windows. 12 | 13 | > Screencast: [How to deploy a Meteor app with Meteor Up (by Sacha Greif)](https://www.youtube.com/watch?v=WLGdXtZMmiI) 14 | 15 | **Table of Contents** 16 | 17 | - [Features](#features) 18 | - [Server Configuration](#server-configuration) 19 | - [SSH-key-based authentication (with passphrase)](#ssh-keys-with-passphrase-or-ssh-agent-support) 20 | - [Installation](#installation) 21 | - [Creating a Meteor Up Project](#creating-a-meteor-up-project) 22 | - [Example File](#example-file) 23 | - [Setting Up a Server](#setting-up-a-server) 24 | - [Deploying an App](#deploying-an-app) 25 | - [Additional Setup/Deploy Information](#additional-setupdeploy-information) 26 | - [Server Setup Details](#server-setup-details) 27 | - [Deploy Wait Time](#deploy-wait-time) 28 | - [Multiple Deployment Targets](#multiple-deployment-targets) 29 | - [Access Logs](#access-logs) 30 | - [Reconfiguring & Restarting](#reconfiguring--restarting) 31 | - [Accessing the Database](#accessing-the-database) 32 | - [Multiple Deployments](#multiple-deployments) 33 | - [Server Specific Environment Variables](#server-specific-environment-variables) 34 | - [SSL Support](#ssl-support) 35 | - [Updating](#updating) 36 | - [Troubleshooting](#troubleshooting) 37 | - [Binary Npm Module Support](#binary-npm-module-support) 38 | - [Additional Resources](#additional-resources) 39 | 40 | ### Features 41 | 42 | * Single command server setup 43 | * Single command deployment 44 | * Multi server deployment 45 | * Environmental Variables management 46 | * Support for [`settings.json`](http://docs.meteor.com/#meteor_settings) 47 | * Password or Private Key(pem) based server authentication 48 | * Access, logs from the terminal (supports log tailing) 49 | * Support for multiple meteor deployments (experimental) 50 | 51 | ### Server Configuration 52 | 53 | * Auto-Restart if the app crashed (using forever) 54 | * Auto-Start after the server reboot (using upstart) 55 | * Stepdown User Privileges 56 | * Revert to the previous version, if the deployment failed 57 | * Secured MongoDB Installation (Optional) 58 | * Pre-Installed PhantomJS (Optional) 59 | 60 | ### Installation 61 | 62 | npm install -g mup 63 | 64 | ### Creating a Meteor Up Project 65 | 66 | mkdir ~/my-meteor-deployment 67 | cd ~/my-meteor-deployment 68 | mup init 69 | 70 | This will create two files in your Meteor Up project directory: 71 | 72 | * mup.json - Meteor Up configuration file 73 | * settings.json - Settings for Meteor's [settings API](http://docs.meteor.com/#meteor_settings) 74 | 75 | `mup.json` is commented and easy to follow (it supports JavaScript comments). 76 | 77 | ### Example File 78 | 79 | ```js 80 | { 81 | // Server authentication info 82 | "servers": [ 83 | { 84 | "host": "hostname", 85 | "username": "root", 86 | "password": "password", 87 | // or pem file (ssh based authentication) 88 | //"pem": "~/.ssh/id_rsa", 89 | // Also, for non-standard ssh port use this 90 | //"sshOptions": { "port" : 49154 }, 91 | // server specific environment variables 92 | "env": {} 93 | } 94 | ], 95 | 96 | // Install MongoDB on the server. Does not destroy the local MongoDB on future setups 97 | "setupMongo": true, 98 | 99 | // WARNING: Node.js is required! Only skip if you already have Node.js installed on server. 100 | "setupNode": true, 101 | 102 | // WARNING: nodeVersion defaults to 0.10.36 if omitted. Do not use v, just the version number. 103 | "nodeVersion": "0.10.36", 104 | 105 | // Install PhantomJS on the server 106 | "setupPhantom": true, 107 | 108 | // Show a progress bar during the upload of the bundle to the server. 109 | // Might cause an error in some rare cases if set to true, for instance in Shippable CI 110 | "enableUploadProgressBar": true, 111 | 112 | // Application name (no spaces). 113 | "appName": "meteor", 114 | 115 | // Location of app (local directory). This can reference '~' as the users home directory. 116 | // i.e., "app": "~/Meteor/my-app", 117 | // This is the same as the line below. 118 | "app": "/Users/arunoda/Meteor/my-app", 119 | 120 | // Configure environment 121 | // ROOT_URL must be set to https://YOURDOMAIN.com when using the spiderable package & force SSL 122 | // your NGINX proxy or Cloudflare. When using just Meteor on SSL without spiderable this is not necessary 123 | "env": { 124 | "PORT": 80, 125 | "ROOT_URL": "http://myapp.com", 126 | "MONGO_URL": "mongodb://arunoda:fd8dsjsfh7@hanso.mongohq.com:10023/MyApp", 127 | "MAIL_URL": "smtp://postmaster%40myapp.mailgun.org:adj87sjhd7s@smtp.mailgun.org:587/" 128 | }, 129 | 130 | // Meteor Up checks if the app comes online just after the deployment. 131 | // Before mup checks that, it will wait for the number of seconds configured below. 132 | "deployCheckWaitTime": 15 133 | } 134 | ``` 135 | 136 | ### Setting Up a Server 137 | 138 | mup setup 139 | 140 | 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. 141 | 142 | ### Deploying an App 143 | 144 | mup deploy 145 | 146 | This will bundle the Meteor project and deploy it to the server. 147 | 148 | ### Additional Setup/Deploy Information 149 | 150 | #### Deploy Wait Time 151 | 152 | 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` 153 | 154 | #### SSH keys with passphrase (or ssh-agent support) 155 | 156 | > This only tested with Mac/Linux 157 | 158 | With the help of `ssh-agent`, `mup` can use SSH keys encrypted with a 159 | passphrase. 160 | 161 | Here's the process: 162 | 163 | * First remove your `pem` field from the `mup.json`. So, your `mup.json` only has the username and host only. 164 | * Then start a ssh agent with `eval $(ssh-agent)` 165 | * Then add your ssh key with `ssh-add ` 166 | * Then you'll asked to enter the passphrase to the key 167 | * After that simply invoke `mup` commands and they'll just work 168 | * Once you've deployed your app kill the ssh agent with `ssh-agent -k` 169 | 170 | #### Ssh based authentication with `sudo` 171 | 172 | **If your username is `root`, you don't need to follow these steps** 173 | 174 | 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.) 175 | 176 | Make sure you also add your ssh key to the ```/YOUR_USERNAME/.ssh/authorized_keys``` list 177 | 178 | You can add your user to the sudo group: 179 | 180 | sudo adduser *username* sudo 181 | 182 | And you also need to add NOPASSWD to the sudoers file: 183 | 184 | sudo visudo 185 | 186 | # replace this line 187 | %sudo ALL=(ALL) ALL 188 | 189 | # by this line 190 | %sudo ALL=(ALL) NOPASSWD:ALL 191 | 192 | When this process is not working you might encounter the following error: 193 | 194 | 'sudo: no tty present and no askpass program specified' 195 | 196 | #### Server Setup Details 197 | 198 | This is how Meteor Up will configure the server for you based on the given `appName` or using "meteor" as default appName. This information will help you customize the server for your needs. 199 | 200 | * your app lives at `/opt//app` 201 | * mup uses `upstart` with a config file at `/etc/init/.conf` 202 | * you can start and stop the app with upstart: `start ` and `stop ` 203 | * logs are located at: `/var/log/upstart/.log` 204 | * MongoDB installed and bound to the local interface (cannot access from the outside) 205 | * the database is named `` 206 | 207 | For more information see [`lib/taskLists.js`](https://github.com/arunoda/meteor-up/blob/master/lib/taskLists.js). 208 | 209 | #### Multiple Deployment Targets 210 | 211 | You can use an array to deploy to multiple servers at once. 212 | 213 | To deploy to *different* environments (e.g. staging, production, etc.), use separate Meteor Up configurations in separate directories, with each directory containing separate `mup.json` and `settings.json` files, and the `mup.json` files' `app` field pointing back to your app's local directory. 214 | 215 | #### Custom Meteor Binary 216 | 217 | Sometimes, you might be using `mrt`, or Meteor from a git checkout. By default, Meteor Up uses `meteor`. You can ask Meteor Up to use the correct binary with the `meteorBinary` option. 218 | 219 | ~~~js 220 | { 221 | ... 222 | "meteorBinary": "~/bin/meteor/meteor" 223 | ... 224 | } 225 | ~~~ 226 | 227 | ### Access Logs 228 | 229 | mup logs -f 230 | 231 | Mup can tail logs from the server and supports all the options of `tail`. 232 | 233 | ### Reconfiguring & Restarting 234 | 235 | After you've edit environmental variables or `settings.json`, you can reconfigure the app without deploying again. Use the following command to do update the settings and restart the app. 236 | 237 | mup reconfig 238 | 239 | If you want to stop, start or restart your app for any reason, you can use the following commands to manage it. 240 | 241 | mup stop 242 | mup start 243 | mup restart 244 | 245 | ### Accessing the Database 246 | 247 | You can't access the MongoDB from the outside the server. To access the MongoDB shell you need to log into your server via SSH first and then run the following command: 248 | 249 | mongo appName 250 | 251 | ### Server Specific Environment Variables 252 | 253 | It is possible to provide server specific environment variables. Add the `env` object along with the server details in the `mup.json`. Here's an example: 254 | 255 | ~~~js 256 | { 257 | "servers": [ 258 | { 259 | "host": "hostname", 260 | "username": "root", 261 | "password": "password", 262 | "env": { 263 | "SOME_ENV": "the-value" 264 | } 265 | } 266 | 267 | ... 268 | } 269 | ~~~ 270 | 271 | By default, Meteor UP adds `CLUSTER_ENDPOINT_URL` to make [cluster](https://github.com/meteorhacks/cluster) deployment simple. But you can override it by defining it yourself. 272 | 273 | ### Multiple Deployments 274 | 275 | Meteor Up supports multiple deployments to a single server. Meteor Up only does the deployment; if you need to configure subdomains, you need to manually setup a reverse proxy yourself. 276 | 277 | Let's assume, we need to deploy production and staging versions of the app to the same server. The production app runs on port 80 and the staging app runs on port 8000. 278 | 279 | We need to have two separate Meteor Up projects. For that, create two directories and initialize Meteor Up and add the necessary configurations. 280 | 281 | In the staging `mup.json`, add a field called `appName` with the value `staging`. You can add any name you prefer instead of `staging`. Since we are running our staging app on port 8000, add an environment variable called `PORT` with the value 8000. 282 | 283 | Now setup both projects and deploy as you need. 284 | 285 | ### SSL Support 286 | 287 | Meteor Up has the built in SSL support. It uses [stud](https://github.com/bumptech/stud) SSL terminator for that. First you need to get a SSL certificate from some provider. This is how to do that: 288 | 289 | * [First you need to generate a CSR file and the private key](http://www.rackspace.com/knowledge_center/article/generate-a-csr-with-openssl) 290 | * Then purchase a SSL certificate. 291 | * Then generate a SSL certificate from your SSL providers UI. 292 | * Then that'll ask to provide the CSR file. Upload the CSR file we've generated. 293 | * When asked to select your SSL server type, select it as nginx. 294 | * Then you'll get a set of files (your domain certificate and CA files). 295 | 296 | 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. 297 | 298 | Then add following configuration to your `mup.json` file. 299 | 300 | ~~~js 301 | { 302 | ... 303 | 304 | "ssl": { 305 | "pem": "./ssl.pem", 306 | //"backendPort": 80 307 | } 308 | 309 | ... 310 | } 311 | ~~~ 312 | 313 | Now, simply do `mup setup` and now you've the SSL support. 314 | 315 | > * By default, it'll think your Meteor app is running on port 80. If it's not, change it with the `backendPort` configuration field. 316 | > * SSL terminator will run on the default SSL port `443` 317 | > * If you are using multiple servers, SSL terminators will run on the each server (This is made to work with [cluster](https://github.com/meteorhacks/cluster)) 318 | > * Right now, you can't have multiple SSL terminators running inside a single server 319 | 320 | ### Updating 321 | 322 | To update `mup` to the latest version, just type: 323 | 324 | npm update mup -g 325 | 326 | You should try and keep `mup` up to date in order to keep up with the latest Meteor changes. But note that if you need to update your Node version, you'll have to run `mup setup` again before deploying. 327 | 328 | ### Troubleshooting 329 | 330 | #### Check Access 331 | 332 | 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. 333 | 334 | #### Check Logs 335 | If you suddenly can't deploy your app anymore, first use the `mup logs -f` command to check the logs for error messages. 336 | 337 | One of the most common problems is your Node version getting out of date. In that case, see “Updating” section above. 338 | 339 | #### Verbose Output 340 | If you need to see the output of `meteor-up` (to see more precisely where it's failing or hanging, for example), run it like so: 341 | 342 | DEBUG=* mup 343 | 344 | where `` is one of the `mup` commands such as `setup`, `deploy`, etc. 345 | 346 | ### Binary Npm Module Support 347 | 348 | Some of the Meteor core packages as well some of the community packages comes with npm modules which has been written in `C` or `C++`. These modules are platform dependent. 349 | So, we need to do special handling, before running the bundle generated from `meteor bundle`. 350 | (meteor up uses the meteor bundle) 351 | 352 | Fortunately, Meteor Up **will take care** of that job for you and it will detect binary npm modules and re-build them before running your app on the given server. 353 | 354 | > * Meteor 0.9 adds a similar feature where it allows package developers to publish their packages for different architecures, if their packages has binary npm modules. 355 | > * As a side effect of that, if you are using a binary npm module inside your app via `meteorhacks:npm` package, you won't be able to deploy into `*.meteor.com`. 356 | > * But, you'll be able to deploy with Meteor Up since we are re-building binary modules on the server. 357 | 358 | ### Additional Resources 359 | 360 | * [Using Meteor Up with Nitrous.io](https://github.com/arunoda/meteor-up/wiki/Using-Meteor-Up-with-Nitrous.io) 361 | * [Change Ownership of Additional Directories](https://github.com/arunoda/meteor-up/wiki/Change-Ownership-of-Additional-Directories) 362 | * [Using Meteor Up with NginX vhosts](https://github.com/arunoda/meteor-up/wiki/Using-Meteor-Up-with-NginX-vhosts) 363 | --------------------------------------------------------------------------------