Skip to content

Instantly share code, notes, and snippets.

@simmo
Created November 19, 2015 09:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simmo/1ec40829b70fb1ec42a7 to your computer and use it in GitHub Desktop.
Save simmo/1ec40829b70fb1ec42a7 to your computer and use it in GitHub Desktop.
Capistano-like Node deployment using Flightplan
'use strict'
var join = require('path').join
var plan = require('flightplan')
const application = 'example.com'
const deployTo = join('/var', 'www', application)
const repoUrl = 'https://github.com/example/example.git'
const branch = 'master'
const keepReleases = 3
const releaseTimestamp = new Date().getTime().toString()
const releasesPath = join(deployTo, 'releases')
const sharedPath = join(deployTo, 'shared')
const repoPath = join(deployTo, 'repo')
const currentPath = join(deployTo, 'current')
const deploymentLogPath = join(deployTo, 'deployments.log')
const releasePath = join(releasesPath, releaseTimestamp)
const linkedDirs = ['node_modules']
var revision = null
var envs = {
production: [
{
host: application,
username: 'xxxx',
agent: process.env.SSH_AUTH_SOCK
}
]
}
var deploy = {
check: (transport) => {
transport.log('Checking environment...')
// 1) Check git is installed
transport.exec('which git', { silent: true })
// 2) Check remote repo exists
transport.exec(`git ls-remote --heads ${repoUrl}`, { silent: true })
// 3) Check releases and shared directories exist
transport.exec(`mkdir -p ${releasesPath} ${sharedPath}`)
// 4) Check shared directories exist
if (linkedDirs.length) {
let dirs = linkedDirs.map(dir => join(sharedPath, dir)).join()
transport.exec(`mkdir -p ${dirs}`)
}
// 5) Check we have a repo
if (transport.exec(`[ -f ${join(repoPath, 'HEAD')} ]`, { failsafe: true, silent: true }).code === 0) {
// Yes: Update
transport.with(`cd ${repoPath}`, () => {
transport.exec(`git remote update`, { silent: true })
})
} else {
// No: Clone
transport.exec(`git clone --mirror ${repoUrl} ${repoPath}`)
}
// 6) Check we have write permissions
// @todo
},
createRelease: (transport) => {
// Create the release
transport.log('Create release...')
// 1) Create release folder
transport.exec(`mkdir -p ${releasePath}`)
// 2) Bundle repo, copy to release and uncompress contents
transport.exec(`git -C ${repoPath} archive ${branch} | tar -x -f - -C ${releasePath}`)
// 3) Create symlinked directories
if (linkedDirs.length) {
let dirs = linkedDirs.map(dir => join(releasePath, dir)).join()
transport.exec(`mkdir -p ${dirs}`)
linkedDirs.forEach(dir => {
let target = join(releasePath, dir)
let source = join(sharedPath, dir)
if (transport.exec(`[ -d ${target} ]`, { failsafe: true, silent: true }).code === 0) {
transport.exec(`rm -rf ${target}`)
}
transport.exec(`ln -s ${source} ${target}`)
})
}
// 4) Fetch revision number
transport.with(`cd ${repoPath}`, () => {
let fetchReleaseSha = transport.exec(`git rev-list --max-count=1 --abbrev-commit --abbrev=12 ${branch}`, { silent: true })
if (fetchReleaseSha.code === 0) {
revision = fetchReleaseSha.stdout.trim()
}
})
// 5) Run npm install and build
transport.with(`cd ${releasePath}; export NODE_ENV=${transport._context.target}`, () => {
transport.exec(`npm install`, { silent: true })
transport.exec(`npm run build`, { silent: true })
})
},
publish: (transport) => {
transport.log('Publishing release...')
// 1) Update current symlink to new release
transport.exec(`ln -sfn ${releasePath} ${currentPath}`)
// 2) Restart node process
transport.exec(`NODE_ENV=production forever restart -c nodejs --spinSleepTime 10000 ${join(currentPath, 'index.js')} || NODE_ENV=production forever start -c nodejs --spinSleepTime 10000 ${join(currentPath, 'index.js')}`, { silent: true })
// 3) Update log
transport.exec(`printf "[%s %s] Branch %s (at %s) deployed as release %s by %s\n" $(date '+%Y-%m-%d %H:%M:%S') "${branch}" "${revision}" "${releaseTimestamp}" $(whoami) >> ${deploymentLogPath}`);
},
cleanup: (transport) => {
transport.log('Cleaning up old releases...')
// 1) Only remove the number of releases specified by keepReleases
var fetchReleases = transport.exec(`ls -r ${releasesPath}`, { silent: true })
if (fetchReleases.code === 0) {
var releases = fetchReleases.stdout.trim().split('\n').slice(keepReleases)
if (releases.length) {
transport.log(`Removing ${releases.length} release(s)...`)
transport.with(`cd ${releasesPath}`, () => transport.exec(`rm -rf ${releases.join(' ')}`))
}
}
}
}
plan.target('production', envs.production)
plan.remote('deploy', deploy.check)
plan.remote('deploy', deploy.createRelease)
plan.remote('deploy', deploy.publish)
plan.remote('deploy', deploy.cleanup)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment