diff --git a/README.md b/README.md index 834dc77..2a79092 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ This version of Meteor Up is powered by [Docker](http://www.docker.com/) and it - [Deploying an App](#deploying-an-app) - [Build Options](#build-options) - [Additional Setup/Deploy Information](#additional-setupdeploy-information) + - [RHEL Support](#rhel-support) - [Server Setup Details](#server-setup-details) - [Deploy Wait Time](#deploy-wait-time) - [Multiple Deployment Targets](#multiple-deployment-targets) @@ -161,6 +162,30 @@ When building the meteor app, we can invoke few options. So, you can mention the ### Additional Setup/Deploy Information +#### RHEL Support + +To install on RHEL7 (tested only on RHEL7 but may work for RHELx and/or CentOS). In addition to the sudoer changes discussed previously there may be an additional sudoer config change. + +##### Server + +The version of RHEL7 used for testing disables requiretty. This presents an issue when mupx attempts to connect and do it business. This is generally enforced by having Defaults requiretty in the /etc/sudoers. RedHat just recently removed this from Fedora and REHL, see Bug 1020147 + +Comment out 'Defaults !requiretty' using visudo. This may also be an issue with how mupx utilizes ssh. See code block below from visudo. + +~~~txt +... +# +# Disable "ssh hostname sudo ", because it will show the password in clear. +# You have to run "ssh -t hostname sudo ". +# +#Defaults requiretty +... +~~~ + +##### mup.json +Identify the server os as RedHat by setting the 'os' attribute to 'rhel'. + + #### Deploy Wait Time Meteor Up checks if the deployment is successful or not just after the deployment. By default, it will wait 15 seconds before the check. You can configure the wait time with the `deployCheckWaitTime` option in the `mup.json` diff --git a/lib/taskLists/rhel.js b/lib/taskLists/rhel.js new file mode 100644 index 0000000..175c2b5 --- /dev/null +++ b/lib/taskLists/rhel.js @@ -0,0 +1,192 @@ +var nodemiral = require('nodemiral'); +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var _ = require('underscore'); + +var SCRIPT_DIR = path.resolve(__dirname, '../../scripts/rhel'); +var TEMPLATES_DIR = path.resolve(__dirname, '../../templates/linux'); + +exports.setup = function(config) { + var taskList = nodemiral.taskList('Setup (rhel)'); + + taskList.executeScript('Installing Docker', { + script: path.resolve(SCRIPT_DIR, 'install-docker.sh') + }); + + taskList.executeScript('Setting up Environment', { + script: path.resolve(SCRIPT_DIR, 'setup-env.sh'), + vars: { + appName: config.appName + } + }); + + if (config.setupMongo) { + taskList.copy('Copying MongoDB configuration', { + src: path.resolve(TEMPLATES_DIR, 'mongodb.conf'), + dest: '/opt/mongodb/mongodb.conf' + }); + + taskList.executeScript('Installing MongoDB', { + script: path.resolve(SCRIPT_DIR, 'install-mongodb.sh') + }); + } + + if (config.ssl) { + taskList.copy('Copying SSL certificate bundle', { + src: config.ssl.certificate, + dest: '/opt/' + config.appName + '/config/bundle.crt' + }); + + taskList.copy('Copying SSL private key', { + src: config.ssl.key, + dest: '/opt/' + config.appName + '/config/private.key' + }); + + taskList.executeScript('Verifying SSL configurations', { + script: path.resolve(SCRIPT_DIR, 'verify-ssl-configurations.sh'), + vars: { + appName: config.appName + } + }); + } + + return taskList; +}; + +exports.deploy = function(bundlePath, env, config) { + var deployCheckWaitTime = config.deployCheckWaitTime; + var appName = config.appName; + var taskList = nodemiral.taskList("Deploy app '" + appName + "' (rhel)"); + + taskList.copy('Uploading bundle', { + src: bundlePath, + dest: '/opt/' + appName + '/tmp/bundle.tar.gz', + progressBar: config.enableUploadProgressBar + }); + + copyEnvVars(taskList, env, appName); + + taskList.copy('Initializing start script', { + src: path.resolve(TEMPLATES_DIR, 'start.sh'), + dest: '/opt/' + appName + '/config/start.sh', + vars: { + appName: appName, + useLocalMongo: config.setupMongo, + port: env.PORT, + sslConfig: config.ssl + } + }); + + deployAndVerify(taskList, appName, env.PORT, deployCheckWaitTime); + + return taskList; +}; + +exports.reconfig = function(env, config) { + var appName = config.appName; + var deployCheckWaitTime = config.deployCheckWaitTime; + + var taskList = nodemiral.taskList("Updating configurations (rhel)"); + + copyEnvVars(taskList, env, appName); + startAndVerify(taskList, appName, env.PORT, deployCheckWaitTime); + + return taskList; +}; + +exports.restart = function(config) { + var taskList = nodemiral.taskList("Restarting Application (rhel)"); + + var appName = config.appName; + var port = config.env.PORT; + var deployCheckWaitTime = config.deployCheckWaitTime; + + startAndVerify(taskList, appName, port, deployCheckWaitTime); + + return taskList; +}; + +exports.stop = function(config) { + var taskList = nodemiral.taskList("Stopping Application (rhel)"); + + //stopping + taskList.executeScript('Stopping app', { + script: path.resolve(SCRIPT_DIR, 'stop.sh'), + vars: { + appName: config.appName + } + }); + + return taskList; +}; + +exports.start = function(config) { + var taskList = nodemiral.taskList("Starting Application (rhel)"); + + var appName = config.appName; + var port = config.env.PORT; + var deployCheckWaitTime = config.deployCheckWaitTime; + + startAndVerify(taskList, appName, port, deployCheckWaitTime); + + return taskList; +}; + +function installStud(taskList) { + taskList.executeScript('Installing Stud', { + script: path.resolve(SCRIPT_DIR, 'install-stud.sh') + }); +} + +function copyEnvVars(taskList, env, appName) { + var env = _.clone(env); + // sending PORT to the docker container is useless. + // It'll run on PORT 80 and we can't override it + // Changing the port is done via the start.sh script + delete env.PORT; + taskList.copy('Sending environment variables', { + src: path.resolve(TEMPLATES_DIR, 'env.list'), + dest: '/opt/' + appName + '/config/env.list', + vars: { + env: env || {}, + appName: appName + } + }); +} + +function startAndVerify(taskList, appName, port, deployCheckWaitTime) { + taskList.execute('Starting app', { + command: "bash /opt/" + appName + "/config/start.sh" + }); + + // verifying deployment + taskList.executeScript('Verifying deployment', { + script: path.resolve(SCRIPT_DIR, 'verify-deployment.sh'), + vars: { + deployCheckWaitTime: deployCheckWaitTime || 10, + appName: appName, + port: port + } + }); +} + +function deployAndVerify(taskList, appName, port, deployCheckWaitTime) { + // deploying + taskList.executeScript('Invoking deployment process', { + script: path.resolve(SCRIPT_DIR, 'deploy.sh'), + vars: { + appName: appName + } + }); + + // verifying deployment + taskList.executeScript('Verifying deployment', { + script: path.resolve(SCRIPT_DIR, 'verify-deployment.sh'), + vars: { + deployCheckWaitTime: deployCheckWaitTime || 10, + appName: appName, + port: port + } + }); +} diff --git a/scripts/rhel/deploy.sh b/scripts/rhel/deploy.sh new file mode 100644 index 0000000..12dd110 --- /dev/null +++ b/scripts/rhel/deploy.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +revert_app (){ + if [[ -d old_app ]]; then + sudo rm -rf app + sudo mv old_app app + sudo stop <%= appName %> || : + sudo start <%= appName %> || : + + echo "Latest deployment failed! Reverted back to the previous version." 1>&2 + exit 1 + else + echo "App did not pick up! Please check app logs." 1>&2 + exit 1 + fi +} + +set -e + +APP_DIR=/opt/<%=appName %> + +# save the last known version +cd $APP_DIR +if [[ -d current ]]; then + sudo rm -rf last + sudo mv current last +fi + +# setup the new version +sudo mkdir current +sudo cp tmp/bundle.tar.gz current/ + +# We temporarly stopped the binary building +# Instead we are building for linux 64 from locally +# That's just like what meteor do +# We can have option to turn binary building later on, +# but not now + +# # rebuild binary module +# cd current +# sudo tar xzf bundle.tar.gz + +# docker run \ +# --rm \ +# --volume=$APP_DIR/current/bundle/programs/server:/bundle \ +# --entrypoint="/bin/bash" \ +# meteorhacks/meteord:binbuild -c \ +# "cd /bundle && bash /opt/meteord/rebuild_npm_modules.sh" + +# sudo rm bundle.tar.gz +# sudo tar czf bundle.tar.gz bundle +# sudo rm -rf bundle +# cd .. + +# start app +sudo bash config/start.sh \ No newline at end of file diff --git a/scripts/rhel/install-docker.sh b/scripts/rhel/install-docker.sh new file mode 100644 index 0000000..d8cfdf5 --- /dev/null +++ b/scripts/rhel/install-docker.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# docker rhel install info +# https://docs.docker.com/engine/installation/linux/rhel/ + +# Required to update system +sudo yum update -y + +# Install docker +sudo yum install docker-engine -y +sudo service docker start diff --git a/scripts/rhel/install-mongodb.sh b/scripts/rhel/install-mongodb.sh new file mode 100644 index 0000000..2bd2311 --- /dev/null +++ b/scripts/rhel/install-mongodb.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e +# we use this data directory for the backward compatibility +# older mup uses mongodb from apt-get and they used this data directory +sudo mkdir -p /var/lib/mongodb + +# refresh mongo docker image +docker pull mongo:latest + +# remove mongodb docker image if it is present +set +e +docker rm -f mongodb +set -e + +# setup mongodb image for local access +# /var/lib/mongodb is preserved +docker run \ + -d \ + --restart=always \ + --publish=127.0.0.1:27017:27017 \ + --volume=/var/lib/mongodb:/data/db \ + --volume=/opt/mongodb/mongodb.conf:/mongodb.conf \ + --name=mongodb \ + mongo mongod -f /mongodb.conf diff --git a/scripts/rhel/setup-env.sh b/scripts/rhel/setup-env.sh new file mode 100644 index 0000000..d897ada --- /dev/null +++ b/scripts/rhel/setup-env.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +sudo mkdir -p /opt/<%= appName %>/ +sudo mkdir -p /opt/<%= appName %>/config +sudo mkdir -p /opt/<%= appName %>/tmp +sudo mkdir -p /opt/mongodb + +sudo chown ${USER} /opt/<%= appName %> -R +sudo chown ${USER} /opt/mongodb -R + +sudo usermod -a -G docker ${USER} diff --git a/scripts/rhel/stop.sh b/scripts/rhel/stop.sh new file mode 100644 index 0000000..5d9e9b8 --- /dev/null +++ b/scripts/rhel/stop.sh @@ -0,0 +1,4 @@ +APPNAME=<%= appName %> + +sudo docker rm -f $APPNAME || : +sudo docker rm -f $APPNAME-frontend || : \ No newline at end of file diff --git a/scripts/rhel/verify-deployment.sh b/scripts/rhel/verify-deployment.sh new file mode 100644 index 0000000..95a24b7 --- /dev/null +++ b/scripts/rhel/verify-deployment.sh @@ -0,0 +1,35 @@ +PORT=<%= port %> +APPNAME=<%= appName %> +APP_PATH=/opt/$APPNAME +START_SCRIPT=$APP_PATH/config/start.sh +DEPLOY_CHECK_WAIT_TIME=<%= deployCheckWaitTime %> + +cd $APP_PATH + +revert_app (){ + docker logs --tail=50 $APPNAME 1>&2 + if [ -d last ]; then + sudo mv last current + sudo bash $START_SCRIPT > /dev/null 2>&1 + + echo " " 1>&2 + echo "=> Redeploying previous version of the app" 1>&2 + echo " " 1>&2 + fi + + echo + echo "To see more logs type 'mup logs --tail=50'" + echo "" +} + +elaspsed=0 +while [[ true ]]; do + sleep 1 + elaspsed=$((elaspsed+1)) + curl localhost:$PORT && exit 0 + + if [ "$elaspsed" == "$DEPLOY_CHECK_WAIT_TIME" ]; then + revert_app + exit 1 + fi +done \ No newline at end of file diff --git a/scripts/rhel/verify-ssl-configuration.sh b/scripts/rhel/verify-ssl-configuration.sh new file mode 100644 index 0000000..54b2faa --- /dev/null +++ b/scripts/rhel/verify-ssl-configuration.sh @@ -0,0 +1,21 @@ +APPNAME=<%= appName %> +DUMMY_SERVER_NAME=$APPNAME-dummy-http-server-for-ssl-check + +sudo docker run \ + -d \ + --name $DUMMY_SERVER_NAME \ + --expose=80 \ + debian /bin/bash -c "while [[ true ]]; do sleep 1; done" + +sleep 3 + +set -e + +sudo docker run \ + --rm \ + --link=$DUMMY_SERVER_NAME:backend \ + --volume=/opt/$APPNAME/config/bundle.crt:/bundle.crt \ + --volume=/opt/$APPNAME/config/private.key:/private.key \ + meteorhacks/mup-frontend-server /verify.sh + +sudo docker rm -f $DUMMY_SERVER_NAME \ No newline at end of file