-
-
Save sahidursuman/4ae97d48266a2014ff58 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'capistrano/recipes/deploy/strategy/copy' | |
set :application, "YOUR APPLICATION NAME" | |
set :location, "YOUR DOMAIN (e.g. myapp.com)" | |
set :repository, "YOUR SOURCE CONTROL REPOSITORY" | |
set :user, "THE USER ON YOUR SERVER THAT WILL BE DEPLOYING" | |
set :local_user, "LOCAL USER" | |
set :database_name, "YOUR DATABASE NAME" | |
set :scm, SET WHAT SOURCE CONTROL METHOD YOU USE | |
set :use_sudo, false | |
set :deploy_to, "/home/#{user}/#{application}" | |
set :deploy_via, :copy | |
set :copy_dir, "/Users/#{local_user}/capistrano" | |
set :copy_remote_dir, "/home/#{user}/capistrano" | |
set :copy_cache, "#{copy_dir}/#{application}" | |
set :copy_exclude, [".hg", ".hgignore", "*.DS_Store", "Icon"] | |
set :version_dir, "production_releases" | |
set :current_dir, "current_production" | |
set :local_database_backup_path, "/Users/#{local_user}/capistrano/db_backups/" | |
set :database_dir, "production_database_backups" | |
set :database_backups_path, File.join(deploy_to, database_dir) | |
set :database_backup_path, File.join(database_backups_path, release_name) | |
_cset(:database_backups) { capture("ls -xt #{database_backups_path}").split } | |
_cset(:previous_database_backup) { database_backups.length > 0 ? File.join(database_backups_path, database_backups[0]) : nil } | |
_cset(:previous_migration_path) { File.join(previous_release, "#{application}/backend/migrations") } | |
_cset(:previous_migrations) { capture("ls -xt #{previous_migration_path}/*.py").split } | |
_cset(:previous_migration_base_name) { previous_migrations.length > 1 ? File.basename(previous_migrations[-2]) : nil } | |
_cset(:previous_migration_name) { previous_migration_base_name ? | |
previous_migration_base_name.chomp(File.extname(previous_migration_base_name)) : nil } | |
role :web, location | |
role :app, location | |
role :db, location, :primary => true | |
# Author: Saikat Chakrabarti, [email protected] | |
# Written: 8-26-2009 | |
# | |
# This recipe is used to deploy a Cappuccino application running with a Django backend using | |
# PostgreSQL as the database. This recipe assumes a lot, but should be a good starting point | |
# for others trying to write similare deploy recipes. I've tried to list these assumptions out | |
# below: | |
# 1. You are using PostgreSQL | |
# 2. You have installed Lee Hambley's railsless deploy (http://github.com/leehambley/railsless-deploy/tree/master) | |
# 3. Your directory structure is as follows: | |
# root | |
# |-- application | |
# |-- manage.py | |
# |-- env_config (your environment config goes here - see comments on Capistrano::Deploy::Strategy::Copy below) | |
# |-- backend (your django application that handles backend operations for your capp app) | |
# |-- frontend (your cappuccino application) | |
# |-- other django files... (urls.py, settings.py, etc.) | |
# |-- Capfile (it should look like the one in Lee Hambley's railsless deploy) | |
# |-- config | |
# |-- deploy.rb (this file) | |
# 4. You will need to change the server_tasks.restart method to fit your server's setup | |
# 5. You are using South (http://south.aeracode.org/) to maintain your database migrations. | |
# | |
# Read the comments in the methods below before using them. | |
# Custom tasks for our hosting environment. | |
namespace :remote do | |
desc <<-DESC | |
Create directory required by copy_remote_dir. | |
DESC | |
task :create_copy_remote_dir, :roles => :app do | |
print " creating #{copy_remote_dir}.\n" | |
run "mkdir -p #{copy_remote_dir}" | |
end | |
end | |
# Custom tasks for our local machine. | |
namespace :local do | |
desc <<-DESC | |
Create directory required by copy_dir. This will create the local cache where capistrano | |
will keep a repository of your code that it just pulls changes into. It also sets up | |
the directory where database backups will be stored locally. | |
DESC | |
task :create_copy_dir do | |
print " creating #{copy_dir}.\n" | |
system "mkdir -p #{copy_dir}" | |
logger.debug "creating #{local_database_backup_path}" | |
system "mkdir -p #{local_database_backup_path}" | |
end | |
end | |
namespace :db do | |
desc <<-DESC | |
Backup the database. This will backup the database on to a path on the server as well | |
as send a copy of the database to :local_database_backup_path. Ideally, you should probably | |
have a separate server or something like Amazon EC3 to maintain your database backups. | |
DESC | |
task :backup, :roles => :db, :only => { :primary => true } do | |
on_rollback { run "rm -rf #{database_backup_path}; true" } | |
logger.debug "Backing up database into #{database_backup_path}" | |
run "pg_dump #{database_name} > #{database_backup_path}" | |
logger.debug "Transferring database to local machine for redundancy" | |
system "scp -P #{port} #{user}@#{location}:#{database_backup_path} #{local_database_backup_path}" | |
end | |
desc <<-DESC | |
WARNING - this will drop the database and then restore it from the latest database backup | |
stored on the server at :database_backups_path. It will fail (without dropping the database) | |
if you have no database backups to restore from. | |
DESC | |
task :restore, :roles => :db, :only => { :primary => true } do | |
if previous_database_backup | |
logger.debug "Restoring database from #{previous_database_backup}..." | |
logger.debug "Dropping the current database" | |
run "dropdb #{database_name}" | |
logger.debug "Recreating the database" | |
run "createdb -T template0 #{database_name}" | |
logger.debug "Restoring the database from backup" | |
run "psql #{database_name} < #{previous_database_backup}" | |
logger.debug "Removing restored backup" | |
run "rm -f #{previous_database_backup}" | |
else | |
logger.important "No previous database backup to restore from." | |
end | |
end | |
desc <<-DESC | |
This will delete the old database backups on the server, keeping only the latest 5. It | |
will not touch the duplicate database backups that are stored locally. | |
DESC | |
task :cleanup, :roles => :db, :only => { :primary => true } do | |
count = fetch(:keep_releases, 5).to_i | |
if count >= database_backups.length | |
logger.important "no old releases to clean up" | |
else | |
logger.info "keeping #{count} of #{database_backups.length} deployed releases" | |
directories = (database_backups - database_backups.last(count)).map { |backup| | |
File.join(database_backups_path, backup) }.join(" ") | |
try_sudo "rm -rf #{directories}" | |
end | |
end | |
end | |
namespace :server_tasks do | |
desc <<-DESC | |
Restarts your webserver. The code right now assumes nginx and fastcgi, so you should | |
change this code. | |
DESC | |
task :restart do | |
logger.debug "Restarting nginx and #{application} fastcgi process" | |
sudo "/etc/init.d/nginx restart" | |
sudo "/usr/local/bin/myapp-restart" | |
end | |
desc <<-DESC | |
Cleans up old database backups and old releases. | |
DESC | |
task :cleanup do | |
deploy.cleanup | |
db.cleanup | |
end | |
end | |
namespace :deploy do | |
desc <<-DESC | |
This will ONLY release your code to the server and restart the server. This will not run | |
any migrations you have. | |
DESC | |
task :code do | |
update | |
server_tasks.restart | |
end | |
desc <<-DESC | |
This is the command that you will usually want to run to do a deploy. This will deploy | |
code and also run migrations. | |
DESC | |
task :default do | |
transaction do | |
update_code | |
symlink | |
db.backup | |
migrate | |
end | |
server_tasks.restart | |
end | |
desc <<-DESC | |
This will run the migrations from :current_release in a transaction. | |
DESC | |
task :migrations do | |
transaction do | |
migrate | |
end | |
end | |
desc <<-DESC | |
Deletes all but the last five releases. | |
DESC | |
task :cleanup, :except => { :no_release => true } do | |
count = fetch(:keep_releases, 5).to_i | |
if count >= releases.length | |
logger.important "no old releases to clean up" | |
else | |
logger.info "keeping #{count} of #{releases.length} deployed releases" | |
directories = (releases - releases.last(count)).map { |release| | |
File.join(releases_path, release) }.join(" ") | |
try_sudo "rm -rf #{directories}" | |
end | |
end | |
desc <<-DESC | |
Prepares one or more servers for deployment. Before you can use any \ | |
of the Capistrano deployment tasks with your project, you will need to \ | |
make sure all of your servers have been prepared with `cap deploy:setup'. When \ | |
you add a new server to your cluster, you can easily run the setup task \ | |
on just that server by specifying the HOSTS environment variable: | |
$ cap HOSTS=new.server.com deploy:setup | |
It is safe to run this task on servers that have already been set up; it \ | |
will not destroy any deployed revisions or data. | |
DESC | |
task :setup, :except => { :no_release => true } do | |
dirs = [deploy_to, releases_path, shared_path, database_backups_path] | |
dirs += shared_children.map { |d| File.join(shared_path, d) } | |
run "#{try_sudo} mkdir -p #{dirs.join(' ')} && #{try_sudo} chmod g+w #{dirs.join(' ')}" | |
end | |
desc <<-DESC | |
[internal] Migrate the database to the :current_release. You will usually want to use | |
deploy:migrations instead of calling this directly, as it does not run in a transaction. | |
DESC | |
task :migrate, :roles => :db, :only => { :primary => true } do | |
on_rollback do | |
if previous_migration_name | |
logger.debug "Migrating database to #{previous_migration_name}" | |
run "#{previous_release}/#{application}/manage.py migrate backend #{previous_migration_name}" | |
else | |
logger.debug "Migrating database to zero" | |
run "#{previous_release}/#{application}/manage.py migrate backend zero" | |
end | |
end | |
logger.debug "Migrating database to #{current_release}" | |
run "#{current_release}/#{application}/manage.py migrate backend" | |
end | |
namespace :rollback do | |
desc <<-DESC | |
Rollback code to the previous release. WARNING - This will NOT rollback migrations, so | |
make sure you rollback any migrations BEFORE calling this. If you call this without | |
rolling back migrations, calling rollback:migrations will migrate to the release before | |
the previous release. | |
DESC | |
task :code do | |
revision | |
server_tasks.restart | |
cleanup | |
end | |
desc <<-DESC | |
Rollback migrations to the previous release. This doesn't actually delete the current | |
release, so calling this multiple times will have the same effect. | |
DESC | |
task :migrations do | |
if previous_migration_name | |
logger.debug "Migrating database to #{previous_migration_name}" | |
run "#{previous_release}/#{application}/manage.py migrate backend #{previous_migration_name}" | |
else | |
logger.debug "Migrating database to zero" | |
run "#{previous_release}/#{application}/manage.py migrate backend zero" | |
end | |
end | |
desc <<-DESC | |
Rolls back the migrations, followed by the code. | |
DESC | |
task :default do | |
migrations | |
revision | |
server_tasks.restart | |
cleanup | |
end | |
end | |
end | |
class Capistrano::Deploy::Strategy::Copy | |
# This is a copy of strategy.deploy! to insert some custom code before the deployment is | |
# copied to the server. Here, after the code is fetched, I first setup the production | |
# configuration. This is very specific to my setup - I have my environment configuration | |
# settings (like the server to use, the secure server to use, the database to hit, etc.) | |
# in production_settings.py and ProductionSettings.j, and in my Django and Cappuccino | |
# code, I import enviornment_settings.py and EnvironmentSettings.j to get the configuration | |
# settings. You should add your custom config setup code in the area after | |
# logger.debug "setting up configuration files for production". After setting up the | |
# config, I press the cappuccino code and then continue with the normal deploy process | |
def deploy! | |
if copy_cache | |
if File.exists?(copy_cache) | |
logger.debug "refreshing local cache to revision #{revision} at #{copy_cache}" | |
system(source.sync(revision, copy_cache)) | |
else | |
logger.debug "preparing local cache at #{copy_cache}" | |
system(source.checkout(revision, copy_cache)) | |
end | |
logger.debug "copying cache to deployment staging area #{destination}" | |
Dir.chdir(copy_cache) do | |
FileUtils.mkdir_p(destination) | |
queue = Dir.glob("*", File::FNM_DOTMATCH) | |
while queue.any? | |
item = queue.shift | |
name = File.basename(item) | |
next if name == "." || name == ".." | |
next if copy_exclude.any? { |pattern| File.fnmatch(pattern, item) } | |
if File.symlink?(item) | |
FileUtils.ln_s(File.readlink(File.join(copy_cache, item)), File.join(destination, item)) | |
elsif File.directory?(item) | |
queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH) | |
FileUtils.mkdir(File.join(destination, item)) | |
else | |
FileUtils.ln(File.join(copy_cache, item), File.join(destination, item)) | |
end | |
end | |
end | |
###### CUSTOM DEPLOY STEPS START HERE ####### | |
logger.debug "setting up configuration files for production" | |
FileUtils.mv(File.join(destination, "#{application}/env_config/production_settings.py"), | |
File.join(destination, "#{application}/environment_settings.py")) | |
FileUtils.mv(File.join(destination, "#{application}/env_config/ProductionSettings.j"), | |
File.join(destination, "#{application}/frontend/EnvironmentSettings.j")) | |
logger.debug "adding frameworks link to frontend directory" | |
FileUtils.mkdir(File.join(destination, "#{application}/frontend/Frameworks")) | |
system "ln -s $CAPP_BUILD/Release/AppKit #{destination}/#{application}/frontend/Frameworks/AppKit" | |
system "ln -s $CAPP_BUILD/Release/Foundation #{destination}/#{application}/frontend/Frameworks/Foundation" | |
system "ln -s $CAPP_BUILD/Release/Objective-J #{destination}/#{application}/frontend/Frameworks/Objective-J" | |
logger.debug "starting cappuccino press" | |
system "press #{destination}/#{application}/frontend #{destination}/#{application}/frontend-pressed" | |
logger.debug "removing index-debug" | |
FileUtils.rm(File.join(destination, "#{application}/frontend-pressed/index-debug.html")) | |
logger.debug "replacing frontend with pressed code" | |
FileUtils.rm_rf(File.join(destination, "#{application}/frontend")) | |
FileUtils.mv(File.join(destination, "#{application}/frontend-pressed"), | |
File.join(destination, "#{application}/frontend")) | |
###### CUSTOM DEPLOY STEPS END HERE ####### | |
else | |
logger.debug "getting (via #{copy_strategy}) revision #{revision} to #{destination}" | |
system(command) | |
if copy_exclude.any? | |
logger.debug "processing exclusions..." | |
if copy_exclude.any? | |
copy_exclude.each do |pattern| | |
delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH) | |
# avoid the /.. trap that deletes the parent directories | |
delete_list.delete_if { |dir| dir =~ /\/\.\.$/ } | |
FileUtils.rm_rf(delete_list.compact) | |
end | |
end | |
end | |
end | |
File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) } | |
logger.trace "compressing #{destination} to #{filename}" | |
Dir.chdir(tmpdir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) } | |
upload(filename, remote_filename) | |
run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}" | |
ensure | |
FileUtils.rm filename rescue nil | |
FileUtils.rm_rf destination rescue nil | |
end | |
end | |
# Callbacks. | |
before 'deploy:setup', 'local:create_copy_dir', 'remote:create_copy_remote_dir' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment