# Require our base library. require 'ash/base' require 'railsless-deploy' configuration = Capistrano::Configuration.respond_to?(:instance) ? Capistrano::Configuration.instance(:must_exist) : Capistrano.configuration(:must_exist) configuration.load do # -------------------------------------------- # Deployment dependencies # # $ cap deploy:check # # -------------------------------------------- depend :remote, :command, 'drush' depend :remote, :command, 'rsync' # -------------------------------------------- # try_sudo configuration # -------------------------------------------- set :use_sudo, true # allow try_sudo methods to actually run via sudo # set(:admin_runner) {"#{user}"} # specify the :admin_runner if you need to run it as another user other than root # Clear out the default prompt (i.e., `sudo -p 'sudo password: '`) to fall back # to just using `sudo` due to the concatenation in the sudo method. # # This assumes that you have set up your SSH user to have passwordless sudo # setup for common commands (e.g., chmod, rm, rsync, etc.) # # (see: https://github.com/capistrano/capistrano/blob/legacy-v2/lib/capistrano/configuration/actions/invocation.rb#L229-L237) set :sudo_prompt, '' # -------------------------------------------- # Setting defaults # -------------------------------------------- proc{_cset( :multisites, {"#{application}" => "#{application}"} )} set :drush_bin, "drush" _cset :dump_options, "" # blank options b/c of MYISAM engine (unless anyone knows options that should be included) # -------------------------------------------- # Ubercart Files/Folders # assumes ubercart files are located # within a files/ubercart directory # for each multisite # -------------------------------------------- set :uc_root, "ubercart" set :uc_downloadable_products_root, "downloadable_products" set :uc_encryption_keys_root, "keys" # -------------------------------------------- # Calling our Methods # -------------------------------------------- after "deploy:setup", "deploy:setup_local" after "deploy:finalize_update", "ash:fixperms" # after "deploy:create_symlink", "drupal:symlink" # workaround for issues with capistrano v2.13.3 and # before/after callbacks not firing for 'deploy:symlink' # or 'deploy:create_symlink' after "deploy", "drupal:symlink" after "drupal:symlink_config_file", "drupal:run_makefiles" after "drupal:symlink","drupal:protect" after "drupal:symlink", "compass" after "drupal:symlink", "drupal:clearcache" after "deploy", "deploy:cleanup" # -------------------------------------------- # Overloaded Methods # -------------------------------------------- namespace :deploy do desc "Setup local files necessary for deployment" task :setup_local do # attempt to create files needed for proper deployment system("cp .htaccess htaccess.dist") unless local_file_exists?("htaccess.dist") end desc "Setup shared application directories and permissions after initial setup" task :setup_shared do # remove Capistrano specific directories try_sudo "rm -Rf #{shared_path}/log" try_sudo "rm -Rf #{shared_path}/pids" try_sudo "rm -Rf #{shared_path}/system" # create shared directories multisites.each_pair do |folder, url| run "mkdir -p #{shared_path}/#{url}/files" end # set correct permissions try_sudo "chmod -R 777 #{shared_path}/*" end desc "[internal] Touches up the released code. This is called by update_code after the basic deploy finishes." task :finalize_update, :roles => :web, :except => { :no_release => true } do # remove shared directories multisites.each_pair do |folder, url| if folder != url run "mv #{latest_release}/sites/#{folder} #{latest_release}/sites/#{url}" end try_sudo "rm -Rf #{latest_release}/sites/#{url}/files" end end namespace :web do desc "Disable the application and show a message screen" task :disable, :roles => :web do multisites.each_pair do |folder, url| run "#{drush_bin} -l #{url} -r #{latest_release} vset --yes site_offline 1" end end desc "Enable the application and remove the message screen" task :enable, :roles => :web do multisites.each_pair do |folder, url| run "#{drush_bin} -l #{url} -r #{latest_release} vdel --yes site_offline" end end end end # -------------------------------------------- # Remote/Local database migration tasks # -------------------------------------------- namespace :db do task :local_export do mysqldump = fetch(:mysqldump, "mysqldump") dump_options = fetch(:dump_options, "") system "#{mysqldump} #{dump_options} --opt -h#{db_local_host} -u#{db_local_user} -p#{db_local_pass} #{db_local_name} | gzip -c --best > #{db_local_name}.sql.gz" end desc "Create a compressed MySQL dumpfile of the remote database" task :remote_export, :roles => :db do mysqldump = fetch(:mysqldump, "mysqldump") dump_options = fetch(:dump_options, "") run "#{mysqldump} #{dump_options} --opt -h#{db_remote_host} -u#{db_remote_user} -p#{db_remote_pass} #{db_remote_name} | gzip -c --best > #{deploy_to}/#{db_remote_name}.sql.gz" end end namespace :backup do desc "Perform a backup of database files" task :db, :roles => :web do if previous_release puts "Backing up the database now and putting dump file in the previous release directory" # create the temporary copy for the release directory # which we'll tarball in the backup:web task run "mkdir -p #{tmp_backups_path}/#{release_name}" now = Time.now.to_s.gsub(/ /, "_") # ignored db tables ignore_tables = fetch(:ignore_tables, []) structure_tables_key = fetch(:structure_tables_key, 'structure-tables') if !ignore_tables.empty? if ignore_tables.is_a?(String) ignore_tables_str = ignore_tables else ignore_tables_str = ignore_tables.join(',') end # define the filenames (include the current_path so the dump file will be within the directory) data_filename = "#{tmp_backups_path}/#{release_name}/#{dbname}_data_dump-#{now}.sql.gz" structure_filename = "#{tmp_backups_path}/#{release_name}/#{dbname}_structure_dump-#{now}.sql.gz" if ignore_tables_str == 'common' skip_tables_opt = "--skip-tables-key" else skip_tables_opt = "--skip-tables-list" end multisites.each_pair do |folder, url| # dump the database structure for the proper environment (structure dump of common tables) run "#{drush_bin} -l #{url} -r #{current_path} sql-dump --structure-tables-key=#{structure_tables_key} | gzip -c --best > #{structure_filename}" # dump the database data for the proper environment run "#{drush_bin} -l #{url} -r #{current_path} sql-dump #{skip_tabls_opt}=#{ignore_tables_str} | gzip -c --best > #{data_filename}" end else multisites.each_pair do |folder, url| # define the filename (include the current_path so the dump file will be within the directory) filename = "#{tmp_backups_path}/#{release_name}/#{folder}_dump-#{now}.sql.gz" # dump the database for the proper environment (skip standard tables) run "#{drush_bin} -l #{url} -r #{current_path} sql-dump --skip-tables-key=common | gzip -c --best > #{filename}" end end else logger.important "no previous release to backup; backup of database skipped" end end end # -------------------------------------------- # Drupal-specific methods # -------------------------------------------- namespace :drupal do desc "Symlink shared directories" task :symlink, :roles => :web, :except => { :no_release => true } do multisites.each_pair do |folder, url| # symlinks the appropriate environment's settings.php file symlink_config_file run "ln -nfs #{shared_path}/#{url}/files #{latest_release}/sites/#{url}/files" run "#{drush_bin} -l #{url} -r #{current_path} vset --yes file_directory_path sites/#{url}/files" end end desc <<-DESC Symlinks the appropriate environment's settings file within the proper sites directory Assumes the environment's settings file will be in one of two formats: settings..php => new default settings.php. => deprecated DESC task :symlink_config_file, :roles => :web, :except => { :no_release => true} do multisites.each_pair do |folder, url| drupal_app_site_dir = " #{latest_release}/sites/#{url}" case true when remote_file_exists?("#{drupal_app_site_dir}/settings.#{stage}.php") run "ln -nfs #{drupal_app_site_dir}/settings.#{stage}.php #{drupal_app_site_dir}/settings.php" when remote_file_exists?("#{drupal_app_site_dir}/settings.php.#{stage}") run "ln -nfs #{drupal_app_site_dir}/settings.php.#{stage} #{drupal_app_site_dir}/settings.php" else logger.important "Failed to symlink the settings.php file in #{drupal_app_site_dir} because an unknown pattern was used" end end end desc "Run drush make files to update core and contributed modules" task :run_makefiles, :roles => :web do makefiles = fetch(:drush_makefiles, nil) begin case true when makefiles.is_a?(NilClass) logger.info "Skipping `drush:run_makefiles` because no makefiles were defined" when makefiles.is_a?(String) run "cd #{latest_release} && #{drush_bin} make #{latest_release}/#{drush_makefiles} -y" when makefiles.is_a?(Array) makefiles.each do |makefile| run "cd #{latest_release} && #{drush_bin} make #{latest_release}/#{makefile} -y" end else logger.important "Failed to run drush make files because `:drush_makefiles` was neither a string nor an array. You can correct this by setting `:drush_makefiles` as such: `set :drush_makefiles, 'ash_core.make'`. Just replace with the appropriate name or filepath to a drush make file." end rescue Exception => e logger.important e.message end end desc "Replace local database paths with remote paths" task :updatedb, :roles => :web, :except => { :no_release => true } do multisites.each_pair do |folder, url| run "#{drush_bin} -l #{url} -r #{current_path} sqlq \"UPDATE {files} SET filepath = REPLACE(filepath,'sites/#{folder}/files','sites/#{url}/files');\"" end end desc "Clear all Drupal cache" task :clearcache, :roles => :web, :except => { :no_release => true } do multisites.each_pair do |folder, url| run "#{drush_bin} -l #{url} -r #{current_path} cache-clear all" end end desc "Protect system files" task :protect, :roles => :web, :except => { :no_release => true } do multisites.each_pair do |folder, url| run "chmod 644 #{latest_release}/sites/#{url}/settings.php*" end end desc <<-DESC Secures the ubercart sensitive information (credit card encryption keys) and downloadable files by moving them from the sites file structure in the Drupal root (public) and moving them to the shared directory DESC namespace :ubercart do task :default, :roles => :web, :except => { :no_release => true } do # setup necessary directories within our shared directory setup_ubercart_shared # move the sites/*/files/downloadable_products # to the shared directory via rsync secure_downloadable_files # move the sites/*/files/keys # to the shared directory secure_encryption_key end desc <<-DESC Creates the ubercart directory within each multisites shared directory structure. Example: shared/abc/ubercart shared/xyz/ubercart DESC task :setup_ubercart_shared, :roles => :web, :except => { :no_release => true } do multisites.each_pair do |folder, url| run "mkdir -p #{shared_path}/#{url}/#{uc_root}" end end desc <<-DESC Moves downloadable files from the public directory (Drupal root) to the shared directories Example: sites/abc/files/ubercart/products sites/xyz/files/ubercart/downloadable_products are moved to: shared/abc/ubercart/products shared/xyz/ubercart/downloadable_products DESC task :secure_downloadable_files, :except => { :no_release => true } do # loop through the multisites and move files multisites.each_pair do |folder, url| run "mkdir -p #{shared_path}/#{url}/#{uc_root}/#{uc_downloadable_products_root}" ubercart_dir = "#{latest_release}/sites/#{url}/files/#{uc_root}/#{uc_downloadable_products_root}" case true when remote_dir_exists?("#{ubercart_dir}") run "rsync -rltDvzog #{ubercart_dir} #{shared_path}/#{url}/#{uc_root}/#{uc_downloadable_products_root}" else logger.important "Failed to rsync the ubercart downloadable products in #{ubercart_dir} because the directory doesn't exist" end # update the ubercart's database tracking of where the # root file path is for downloadable products. This should # be set as relative to the root of the drupal directory run "#{drush_bin} -l #{url} -r #{latest_release} vset --yes uc_file_base_dir ../../shared/#{url}/#{uc_root}/#{uc_downloadable_products_root}" end end desc <<-DESC Moves encryption key files from the public directory (Drupal root) to the shared directories Example: sites/abc/files/ubercart/keys sites/xyz/files/ubercart/keys are moved to: shared/abc/ubercart/keys shared/xyz/ubercart/keys DESC task :secure_encryption_key, :roles => :web, :except => { :no_release => true } do # loop through the multisites and move keys multisites.each_pair do |folder, url| run "mkdir -p #{shared_path}/#{url}/#{uc_root}/#{uc_encryption_keys_root}" # update the ubercart's database tracking of where the # root file path is for encryption keys. This should # be set as relative to the root of the drupal directory run "#{drush_bin} -l #{url} -r #{latest_release} vset --yes uc_credit_encryption_path ../../shared/#{url}/#{uc_root}/#{uc_encryption_keys_root}" end end end end end