Capistrano::Configuration.instance(:must_exist).load do require 'capistrano/recipes/deploy/scm' require 'capistrano/recipes/deploy/strategy' require 'railsless-deploy' # ========================================================================= # These variables MUST be set in the client capfiles. If they are not set, # the deploy will fail with an error. # ========================================================================= _cset(:db_type) { abort "Please specify the Drupal database type (:db_type)." } _cset(:db_name) { abort "Please specify the Drupal database name (:db_name)." } _cset(:db_username) { abort "Please specify the Drupal database username (:db_username)." } _cset(:db_password) { abort "Please specify the Drupal database password (:db_password)." } _cset(:db_host) { abort "Please specify the Drupal database host (:db_host)." } # ========================================================================= # These variables may be set in the client capfile if their default values # are not sufficient. # ========================================================================= set :use_sudo, false set :scm, :git set :deploy_via, :remote_cache set :branch, "master" set :git_enable_submodules, true set :keep_releases, 5 set :runner_group, "www-data" set :group_writable, false set (:deploy_to) {"/var/www/#{application}"} set :app_path, "" set :shared_children, ['sites/default/files'] set :shared_files, ['sites/default/settings.php'] set :backup_database, true set :dbbackups, "db_backups" _cset(:dbbackups_path) { File.join(deploy_to, dbbackups) } set :local_database, nil set :push_dump_enabled, false set :download_drush, false _cset(:drush_cmd) { download_drush ? "#{shared_path}/drush/drush" : "drush" } default_run_options[:pty] = true ssh_options[:forward_agent] = true if download_drush depend :remote, :command, "curl" end # This is an optional step that can be defined. #after "deploy", "git:push_deploy_tag" # ========================= # Deploy methods # ========================= namespace :deploy do 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 if download_drush drush.get end dirs = [deploy_to, releases_path, shared_path, dbbackups].join(' ') run "#{try_sudo} mkdir -p #{releases_path} #{shared_path} #{dbbackups_path}" run "#{try_sudo} chown -R #{user}:#{runner_group} #{deploy_to}" if shared_children.size > 0 sub_dirs = shared_children.map { |d| File.join(shared_path, d) } run "#{try_sudo} mkdir -p #{sub_dirs.join(' ')}" run "#{try_sudo} chmod 2775 #{sub_dirs.join(' ')}" end #create drupal config file configuration = drupal_settings() put configuration, "#{shared_path}/#{shared_files[0]}" end desc <<-DESC Deploys your Drupal site, runs drush:update. It supposes that the Setup task was already executed. This overrides the default Capistrano Deploy task to handle database operations and backups, all of them via Drush. DESC task :default do update manage.dbdump_previous end after "deploy", "deploy:cc" desc "Setup a drupal site from scratch" task :cold do setup update manage.push_dump end after "deploy:cold", "deploy:cc" desc "[internal] Rebuild files and settings symlinks" task :finalize_update, :except => { :no_release => true } do if shared_children # Creating symlinks for shared directories shared_children.each do |link| run "#{try_sudo} sh -c 'if [ -d #{release_path}/#{link} ] ; then rm -rf #{release_path}/#{link}; fi'" run "#{try_sudo} ln -nfs #{shared_path}/#{link} #{release_path}/#{link}" end end if shared_files # Creating symlinks for shared files shared_files.each do |link| link_dir = File.dirname("#{shared_path}/#{link}") run "#{try_sudo} ln -nfs #{shared_path}/#{link} #{release_path}/#{link}" end end end desc <<-DESC Removes old releases and corresponding DB backups. 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" old_releases = (releases - releases.last(count)) directories = old_releases.map { |release| File.join(releases_path, release) }.join(" ") databases = old_releases.map { |release| File.join(dbbackups_path, "#{release}.sql")}.join(" ") if backup_database run "rm -rf #{directories} #{databases}" end end task :cc do drush.site_offline drush.updatedb drush.cache_clear drush.site_online drush.cache_clear end namespace :rollback do desc <<-DESC Go back to the previous release (code and database) DESC task :default do revision db_rollback cleanup end desc <<-DESC [internal] Removes the most recently deployed release. This is called by the rollback sequence, and should rarely (if ever) need to be called directly. DESC task :cleanup, :except => { :no_release => true } do run "if [ `readlink #{current_path}` != #{current_release} ]; then rm -rf #{current_release}; fi" end desc <<-DESC [internal] Points the current, files, and settings symlinks at the previous revision. DESC task :revision, :except => { :no_release => true } do if previous_release run "rm #{current_path}" run "ln -s #{previous_release} #{current_path}" else abort "could not rollback the code because there is no prior release" end end desc <<-DESC [internal] If a database backup from the previous release is found, dump the current database and import the backup. This task should NEVER be called standalone. DESC task :db_rollback, :except => { :no_release => true } do if previous_release logger.info "Dumping current database and importing previous one (If one is found)." previous_db = File.join(dbbackups_path, "#{releases[-2]}.sql") import_cmd = "cd #{current_path}/#{app_path} && #{drush_cmd} sql-drop -y && #{drush_cmd} sql-cli < #{previous_db} && rm #{previous_db}" run "if [ -e #{previous_db} ]; then #{import_cmd}; fi" else abort "could not rollback the database because there is no prior release db backups" end end end end # ========================= # Files methods # ========================= namespace :files do desc "Pull drupal sites files (from remote to local)" task :pull, :roles => :app, :except => { :no_release => true } do remote_files_dir = "#{current_path}/sites/default/files/" local_files_dir = "sites/default/files/" run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress #{user}@#{roles[:app].servers[0].host}:#{remote_files_dir} #{local_files_dir}") end desc "Push drupal sites files (from local to remote)" task :push, :roles => :app, :except => { :no_release => true } do remote_files_dir = "#{current_path}/sites/default/files/" local_files_dir = "sites/default/files/" run_locally("rsync --recursive --times --rsh=ssh --compress --human-readable --progress #{local_files_dir} #{user}@#{roles[:app].servers[0].host}:#{remote_files_dir}") end end # ========================= # Git methods # ========================= namespace :git do desc "Place release tag into Git and push it to origin server." task :push_deploy_tag do user = `git config --get user.name` email = `git config --get user.email` tag = "release_#{release_name}" if exists?(:stage) tag = "#{stage}_#{tag}" end puts `git tag #{tag} #{revision} -m "Deployed by #{user} <#{email}>"` puts `git push origin tag #{tag}` end end # ========================= # Drush methods # ========================= namespace :drush do desc "Gets drush and installs it" task :get, :roles => :app, :except => { :no_release => true } do run "#{try_sudo} cd #{shared_path} && curl -O -s http://ftp.drupal.org/files/projects/drush-7.x-5.8.tar.gz && tar -xf drush-7.x-5.8.tar.gz && rm drush-7.x-5.8.tar.gz" run "#{try_sudo} cd #{shared_path} && chmod u+x drush/drush" end desc "Set the site offline" task :site_offline, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} vset site_offline 1 -y" run "#{drush_cmd} -r #{latest_release}/#{app_path} vset maintenance_mode 1 -y" end desc "Backup the database" task :backupdb, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} bam-backup" end desc "Run Drupal database migrations if required" task :updatedb, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} updatedb -y" end desc "Clear the drupal cache" task :cache_clear, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} cache-clear all" end desc "Revert feature" task :feature_revert, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} features-revert-all -y" end desc "Set the site online" task :site_online, :on_error => :continue do run "#{drush_cmd} -r #{latest_release}/#{app_path} vset site_offline 0 -y" run "#{drush_cmd} -r #{latest_release}/#{app_path} vset maintenance_mode 0 -y" end end # ========================= # Manage methods # ========================= namespace :manage do desc "Block bots via robots.txt" task :block_robots do put "User-agent: *\nDisallow: /", "#{current_path}/#{app_path}/robots.txt" end task :dbdump_previous do #Backup the previous release's database if previous_release && backup_database run "cd #{current_path}/#{app_path} && #{drush_cmd} sql-dump > #{ File.join(dbbackups_path, "#{releases[-2]}.sql") }" end end desc 'Dump remote database and restore locally' task :pull_dump do abort("NO LOCAL DATABASE FOUND, set :local_database in the config file..") unless local_database set(:runit, Capistrano::CLI.ui.ask("WARNING!! will overwrite this local database: '#{local_database}', type 'yes' to continue: ")) if runit == 'yes' sql_file = File.join(dbbackups_path, "#{releases.last}-pull.sql") # dump & gzip remote file run "cd #{current_path}/#{app_path} && #{drush_cmd} sql-dump > #{sql_file} && gzip -f #{sql_file}" # copy to local system "if [ ! -d build ]; then mkdir build; fi" # create build folder locally if needed download "#{sql_file}.gz", "build/", :once => true, :via => :scp run "rm #{sql_file}.gz" # extract and restore system "gunzip -f build/#{File.basename(sql_file)}.gz && drush sql-drop -y && drush sql-cli < build/#{File.basename(sql_file)}" if local_database system "rm build/#{File.basename(sql_file)}" end end desc 'Dump local database and restore remote' task :push_dump do abort("NO LOCAL DATABASE FOUND, set :local_database in the config file..") unless local_database abort("THIS STAGE: #{stage} DOES NOT SUPPORT manage:push_dump") unless push_dump_enabled set(:runit, Capistrano::CLI.ui.ask("WARNING!! will overwrite this REMOTE database: '#{db_name}', type 'yes' to continue: ")) if runit == 'yes' sql_file = "#{Time.now.to_i}.sql" system "if [ ! -d build ]; then mkdir build; fi" # create build folder locally if needed # dump & gzip local file system "drush sql-dump > ./build/#{sql_file} && gzip ./build/#{sql_file}" # copy to remote upload "build/#{sql_file}.gz", File.join(dbbackups_path, "#{sql_file}.gz"), :once => true, :via => :scp system "rm build/#{sql_file}.gz" # extract and restore run "gunzip -f #{File.join(dbbackups_path, "#{sql_file}.gz")} && cd #{current_path}/#{app_path} && #{drush_cmd} sql-drop -y && #{drush_cmd} sql-cli < #{File.join(dbbackups_path, "#{sql_file}")}" run "rm #{File.join(dbbackups_path, "#{sql_file}")}" end end end # ========================= # Helper methods # ========================= # Builds initial contents of the Drupal website's settings file def drupal_settings() settings = <<-STRING array ('default' => array ( 'database' => '#{db_name}', 'username' => '#{db_username}', 'password' => '#{db_password}', 'host' => '#{db_host}', 'port' => '', 'driver' => '#{db_type}', 'prefix' => '', ))); ini_set('session.gc_probability', 1); ini_set('session.gc_divisor', 100); ini_set('session.gc_maxlifetime', 200000); ini_set('session.cookie_lifetime', 2000000); $conf['404_fast_paths_exclude'] = '/\/(?:styles)\//'; $conf['404_fast_paths'] = '/\.(?:txt|png|gif|jpe?g|css|js|ico|swf|flv|cgi|bat|pl|dll|exe|asp)$/i'; $conf['404_fast_html'] = '
The requested URL "@path" was not found on this server.
'; // Allow local env to override settings by creating a local.settings.php. $path = str_replace('settings.php', 'local.settings.php', __FILE__); if (file_exists($path)) { include_once($path); } STRING end end