Last active
January 1, 2016 05:39
-
-
Save mmrwoods/8100006 to your computer and use it in GitHub Desktop.
Dumping ground for capistrano recipes...
This file contains hidden or 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
namespace :account do | |
desc "Change the deploy user password" | |
task :reset_password do | |
deploy_user = user | |
deploy_pass = generate_password | |
as_sudo_user do | |
find_servers.each do |server| | |
if read_file("/etc/passwd", :hosts => server.host).lines.grep(/#{deploy_user}:/).any? | |
run "echo \"#{deploy_user}:#{deploy_pass}\" | #{sudo} chpasswd", :hosts => server.host | |
logger.important "Password for deploy user set to '#{deploy_pass}' on #{server.host}" | |
else | |
logger.important "Password not set on #{server.host}, deploy user not found" | |
end | |
end | |
end | |
end | |
namespace :setup do | |
desc "Creates deploy user and configures pubkey auth" | |
task :default do | |
create_user_account | |
set_group_membership | |
configure_ssh_keys | |
end | |
task :create_user_account do | |
deploy_user = user | |
deploy_pass = generate_password | |
as_sudo_user do | |
find_servers.each do |server| | |
if read_file("/etc/passwd", :hosts => server.host).lines.grep(/#{deploy_user}:/).empty? | |
sudo "useradd -m #{deploy_user}", :hosts => server.host | |
logger.important "Deploy user created on #{server.host}" | |
run "echo \"#{deploy_user}:#{deploy_pass}\" | #{sudo} chpasswd", :hosts => server.host | |
ui.say "Note: password for deploy user set to #{deploy_pass}" | |
else | |
logger.important "Deploy user not created on #{server.host}, account exists" | |
end | |
end | |
end | |
end | |
task :set_group_membership do | |
deploy_user = user | |
as_sudo_user do | |
find_servers.each do |server| | |
# FIXME: this tries to add the user to the apache group on servers without apache | |
%w{apache rvm}.each do |group| | |
unless read_file("/etc/group", :hosts => server.host).lines.grep(/#{group}/).empty? | |
# groups exists, ensure user is member | |
unless capture("groups #{deploy_user}", :hosts => server.host).chomp.match(/\b#{group}\b/) | |
# user not in group, add... | |
sudo "usermod -a -G #{group} #{deploy_user}" | |
end | |
if group == "apache" | |
# set apache as default/login group | |
sudo "usermod -g apache #{deploy_user}", :hosts => server.host | |
end | |
end | |
end | |
end | |
end | |
end | |
task :configure_ssh_keys do | |
deploy_user = user | |
sshdir = "/home/#{deploy_user}/.ssh" | |
pubkey = `cat ~/.ssh/id_rsa.pub` | |
as_sudo_user do | |
sudo "mkdir -p #{sshdir}" | |
sudo "touch #{sshdir}/authorized_keys" | |
sudo "mv #{sshdir}/authorized_keys /tmp/" | |
sudo "chown #{user} /tmp/authorized_keys" | |
sudo "echo \"#{pubkey}\" >> /tmp/authorized_keys" | |
sudo "mv /tmp/authorized_keys #{sshdir}/" | |
sudo "chmod 700 #{sshdir}" | |
sudo "chmod 600 #{sshdir}/authorized_keys" | |
sudo "chown -R #{deploy_user} #{sshdir}" | |
end | |
end | |
end | |
namespace :check do | |
desc "Checks that pubkey auth works and you can run commands as root" | |
task :default do | |
auth_keys | |
sudo_user | |
end | |
task :auth_keys do | |
run "echo `whoami`" | |
end | |
task :sudo_user do | |
as_sudo_user do | |
# FIXME: should not be aware of rvm | |
sudo "echo `whoami`", :shell => fetch(:rvm_install_shell,:bash) | |
end | |
end | |
end | |
end |
This file contains hidden or 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
# reload after making configuration changes | |
after "apache:configure", "apache:reload" | |
after "apache:setup", "apache:reload" | |
after "apache:deploy:setup", "apache:reload" | |
_cset(:apache_vhost_template) { "vhost_http.conf" } | |
_cset(:apache_server_name) do | |
ui.ask("Enter server name for apache vhost (e.g. www.myapp.com):") | |
end | |
namespace :apache do | |
desc "Installs apache web server using yum" | |
task :install, :roles => :web do | |
yum :install, :httpd, "httpd-devel", :mod_ssl | |
service :httpd, :start | |
end | |
desc "Restarts apache/httpd service" | |
task :restart, :roles => :web do | |
service :httpd, :restart | |
end | |
task :reload, :roles => :web do | |
# Note: reloading is roughly equivalent to a graceful restart | |
service :httpd, :reload | |
end | |
namespace :configure do | |
desc "Configures apache web server" | |
task :default do | |
start_on_boot | |
remove_welcome_page | |
upload_security_conf | |
end | |
task "start_on_boot", :roles => :web do | |
as_sudo_user do | |
sudo "chkconfig httpd on" | |
end | |
end | |
task "remove_welcome_page", :roles => :web do | |
as_sudo_user do | |
sudo "rm -rf /etc/httpd/conf.d/welcome.conf" | |
end | |
end | |
task :upload_security_conf, :roles => :web do | |
as_sudo_user do | |
upload_resource "apache/security.conf", "/etc/httpd/conf.d/security.conf", :sudo => true | |
copy_permissions "/etc/httpd/conf/httpd.conf", "/etc/httpd/conf.d/security.conf" | |
end | |
end | |
end | |
namespace :setup do | |
desc "Prepares apache for deployments in general" | |
task :default do | |
configure_virtual_hosts | |
configure_ssl | |
end | |
task :configure_virtual_hosts, :roles => :web do | |
as_sudo_user do | |
config_file = "/etc/httpd/conf/httpd.conf" | |
tmp_file = "/tmp/httpd.conf" | |
if capture("tail -n 50 #{config_file}").lines.grep("Include vhost.d/*").empty? | |
sudo "mkdir -p /etc/httpd/vhost.d" | |
sudo "sed '/^NameVirtualHost/d' #{config_file} | sed '/Include vhost.d/d' > #{tmp_file}" | |
sudo "echo 'NameVirtualHost *:80' >> #{tmp_file}" | |
sudo "echo 'NameVirtualHost *:443' >> #{tmp_file}" | |
sudo "echo 'Include vhost.d/*.conf' >> #{tmp_file}" | |
keep_original config_file | |
sudo "mv #{tmp_file} #{config_file}" | |
copy_original_permissions config_file | |
else | |
logger.important "Warning: apache already configured for name based virtual hosts" | |
end | |
end | |
end | |
task :configure_ssl, :roles => :web do | |
as_sudo_user do | |
config_file = "/etc/httpd/conf.d/ssl.conf" | |
tmp_file = "/tmp/ssl.conf" | |
unless read_file(config_file).lines.grep(/^<VirtualHost/).empty? | |
# strip virtual host declaration from general ssl configuration | |
sudo "sed -n '/^<VirtualHost/q;p' #{config_file} > #{tmp_file}" | |
keep_original config_file | |
sudo "mv #{tmp_file} #{config_file}" | |
copy_original_permissions config_file | |
end | |
# upload SSL certificates and shared configuration file | |
sudo "mkdir -p /etc/httpd/ssl" | |
Dir.glob("config/deploy/resources/ssl/*.*").each do |path| | |
resource = path.sub("config/deploy/resources/","") | |
upload_resource resource, "/tmp/#{File.basename(path)}" | |
sudo "mv /tmp/#{File.basename(path)} /etc/httpd/ssl/" | |
end | |
sudo "chown root:root /etc/httpd/ssl/*" | |
sudo "chmod 644 /etc/httpd/ssl/*" | |
sudo "chmod 600 /etc/httpd/ssl/*.key" | |
end | |
end | |
end | |
namespace :deploy do | |
desc "Prepares apache for a specific deploy target" | |
task :setup, :roles => :web do | |
as_sudo_user do | |
template_name = fetch(:apache_vhost_template) | |
destination = "/etc/httpd/vhost.d/#{fetch(:apache_server_name)}.conf" | |
upload_template template_name, destination, :sudo => true | |
end | |
end | |
end | |
end |
This file contains hidden or 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
namespace :backup do | |
desc "Installs daily filesystem backup script" | |
task :install do | |
as_sudo_user do | |
ui.say "Uploading backup script..." | |
upload_resource "backup/dumb_ass_backup.rb", "/usr/local/sbin/dumb_ass_backup.rb", | |
:mode => "700", :owner => "root", :group => "root" | |
end | |
end | |
namespace :configure do | |
desc "Configures daily backups for each server" | |
task :default do | |
write_configuration | |
upload_crontab | |
end | |
task :write_configuration do | |
as_sudo_user do | |
# default configuration for dumb ass backup script | |
config = { | |
"sources" => { | |
"etc" => "/etc", | |
"crontabs" => "/var/spool/cron" | |
}, | |
"destination" => "/var/local/backup" | |
} | |
find_servers.each do |server| | |
ui.say "Generating backup configuration..." | |
if has_directory("/var/lib/pgsql", :hosts => server.host) | |
# postgres installed, add backups to sources | |
config["sources"]["postgres"] = "/var/lib/pgsql/**/backups/*" | |
end | |
if has_directory("/var/lib/aide", :hosts => server.host) | |
# aide installed, add db file to sources | |
config["sources"]["aide"] = "/var/lib/aide/aide.db.gz" | |
end | |
content = config.to_yaml | |
ui.say "Creating destination directory..." | |
sudo "mkdir -p #{config["destination"]}", :hosts => server.host | |
ui.say "Uploading backup configuration..." | |
destination = "/tmp/dumb_ass_backup.yml" | |
upload_content content, destination, :hosts => server.host, :mode => "644" | |
sudo "chown root:root /tmp/dumb_ass_backup.yml", :hosts => server.host | |
sudo "mv /tmp/dumb_ass_backup.yml /usr/local/etc/", :hosts => server.host | |
end | |
end | |
end | |
task :upload_crontab do | |
as_sudo_user do | |
ui.say "Uploading backup crontab file..." | |
upload_resource "backup/crontab", "/etc/cron.d/backup", | |
:mode => "644", :owner => "root", :group => "root" | |
end | |
end | |
end | |
end |
This file contains hidden or 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 'yaml' | |
require 'bundler/capistrano' | |
require 'capistrano/ext/multistage' | |
require 'active_support/core_ext/numeric/time' | |
require 'securerandom' | |
default_run_options[:pty] = true | |
ssh_options[:forward_agent] = true | |
set :scm, :git | |
set(:deploy_to) { "/var/www/#{application}" } | |
set :user, "deploy" | |
set :rake, "bundle exec rake" | |
set :use_sudo, false | |
set(:bundle_flags) { | |
# install gem bundle from local cache if packaged gems found | |
if system("git ls-files | grep 'vendor/cache/' > /dev/null") | |
"--deployment --local" | |
else | |
"--deployment" | |
end | |
} | |
set(:deploy_via) { :remote_cache } | |
set(:copy_exclude) { ".git" } | |
# deploy current branch by default | |
set(:branch) do | |
current_branch = `git branch`.lines.grep(/\*/).first.delete("* ").chomp | |
if current_branch != "master" | |
logger.important "Setting branch to '#{current_branch}'" | |
print "Continuing in " | |
9.downto(0) do |n| | |
print n; sleep 1; print "\b" | |
end | |
print "\n" | |
end | |
current_branch | |
end | |
# User for tasks that require sudo. By default, tasks that need to | |
# run commands as a user with sudo privileges will prompt for the | |
# username. Override this behaviour by just setting the sudo user. | |
set(:sudo_user) do | |
ui.ask("Enter user for sudo: "){|q| q.validate = /\w/} | |
end | |
def ui | |
Capistrano::CLI.ui | |
end | |
def test_remotely(expression, opts={}) | |
self.send( | |
opts[:sudo] ? :sudo_capture : :capture, | |
"test #{expression}; echo $?", | |
opts | |
).chomp == "0" | |
end | |
def has_file(path, opts={}) | |
test_remotely "-f #{path}", opts | |
end | |
def has_directory(path, opts={}) | |
test_remotely "-d #{path}", opts | |
end | |
def has_stdout(expression, opts={}) | |
self.send( | |
opts[:sudo] ? :sudo_capture : :capture, | |
"#{expression} 2> /dev/null", | |
opts | |
).chomp != "" | |
end | |
def has_repo(repo_id) | |
has_stdout("yum repolist | egrep '^\**#{repo_id}'") | |
end | |
def read_file(path, opts={}) | |
self.send( | |
opts[:sudo] ? :sudo_capture : :capture, | |
"cat #{path}", | |
opts | |
).chomp | |
end | |
def generate_password(length=8) | |
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789' | |
password = '' | |
length.times { |i| password << chars[rand(chars.length)] } | |
password | |
end | |
# Helper method to temporarily switch users in the middle of a run. | |
# The primary use case is to switch to a user other than the deploy | |
# user before attempting to execute a command via sudo, as the | |
# deploy user does not always have sufficient privileges to run | |
# commands as root, yet we have tasks which require root privileges. | |
# | |
# Shamelessly stolen from Paul Gross's blog post at: | |
# http://www.pgrs.net/2008/08/06/switching-users-during-a-capistrano-deploy/ | |
def as_user(new_user, &block) | |
old_user = fetch(:user) | |
if old_user == new_user | |
yield | |
return | |
end | |
set :user, new_user | |
sessions.values.each { |session| session.close } | |
sessions.clear | |
yield | |
set :user, old_user | |
sessions.values.each { |session| session.close } | |
sessions.clear | |
end | |
def as_sudo_user(&block) | |
# hack: force the password prompt to appear immediately | |
# note: setting :sudo_prompt does not change the prompt | |
silence_logger do | |
as_user(sudo_user) { sudo "echo foo", :shell => :sh } | |
end | |
as_user(sudo_user, &block) | |
end | |
def silence_logger | |
old_log_level = logger.level | |
logger.level = Logger::IMPORTANT | |
yield | |
ensure | |
logger.level = old_log_level | |
end | |
# Note: cannot use capture with sudo | |
def sudo_capture(cmd, opts={}) | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
sudo "#{cmd} > #{tmp_file}", opts | |
output = capture("cat #{tmp_file}", opts) | |
ensure | |
sudo "rm -rf #{tmp_file}", opts | |
end | |
def yum(action, *packages) | |
# possible TODO: prevent install action from upgrading automagically | |
as_sudo_user do | |
# FIXME: should not be aware of rvm | |
sudo "yum -y #{action.to_s} #{packages.join(' ')}", :shell => fetch(:rvm_install_shell,:bash) | |
end | |
end | |
def service(name, action) | |
as_sudo_user do | |
sudo "service #{name} #{action}" | |
end | |
end | |
def update_configuration(config_file, param, value) | |
# TODO: raise exception or at least warn when setting not found | |
logger.important "update config #{param}=#{value} -> #{config_file}" | |
keep_original config_file | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
# FIXME: will match multiple lines if param found both commented out and not | |
sudo "sed 's/^#*[ \t]*#{param}[ \t]*=.*/#{param}\=#{value}/' #{config_file} > #{tmp_file}" | |
copy_permissions config_file, tmp_file | |
sudo "mv #{tmp_file} #{config_file}" | |
rescue | |
sudo "rm -rf #{tmp_file}" if defined? tmp_file | |
raise $! | |
end | |
def upload_template(template_name, destination, opts={}) | |
path = "config/deploy/templates/#{template_name}.erb" | |
content = ERB.new(File.read(path), 0, "%<>").result(binding) | |
logger.important "upload template #{path} -> #{destination}" | |
ui.say ui.color(content.strip, :blue) | |
if opts[:sudo] || opts[:owner] || opts[:group] | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
put content, tmp_file, opts | |
sudo "chown #{opts[:owner]} #{tmp_file}" if opts[:owner] | |
sudo "chgrp #{opts[:group]} #{tmp_file}" if opts[:group] | |
sudo "mv #{tmp_file} #{destination}" | |
else | |
put content, destination, opts | |
end | |
end | |
def upload_content(content, destination, opts={}) | |
logger.important "upload content -> #{destination}" | |
ui.say ui.color(content.strip, :blue) | |
if opts[:sudo] || opts[:owner] || opts[:group] | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
put content, tmp_file, opts | |
sudo "chown #{opts[:owner]} #{tmp_file}" if opts[:owner] | |
sudo "chgrp #{opts[:group]} #{tmp_file}" if opts[:group] | |
sudo "mv #{tmp_file} #{destination}" | |
else | |
put content, destination, opts | |
end | |
end | |
def upload_resource(resource, destination, opts={}) | |
path = "config/deploy/resources/#{resource}" | |
logger.important "upload resource #{path} -> #{destination}" | |
if opts[:sudo] || opts[:owner] || opts[:group] | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
upload path, tmp_file, opts | |
sudo "chown #{opts[:owner]} #{tmp_file}" if opts[:owner] | |
sudo "chgrp #{opts[:group]} #{tmp_file}" if opts[:group] | |
sudo "mv #{tmp_file} #{destination}" | |
else | |
upload path, destination, opts | |
end | |
end | |
def copy_permissions(reference, target) | |
sudo "chmod --reference=#{reference} #{target}" | |
sudo "chown --reference=#{reference} #{target}" | |
end | |
def keep_original(path) | |
# keep a copy of the original config file, and don't overwrite it once created | |
path = capture("readlink -f #{path}").chomp if path.match(/^~/) # expand path if relative to home dir | |
sudo "cp -p --no-clobber #{path} #{path}.original" | |
end | |
def copy_original_permissions(path) | |
copy_permissions("#{path}.original", path) | |
end | |
def package_gem(name, version=">=0") | |
tmp_dir = "/tmp/#{name}-pack" | |
target_dir = "#{Dir.pwd}/vendor/#{name}" | |
run_locally "rm -rf #{tmp_dir}" | |
run_locally "mkdir -p #{target_dir}" | |
run_locally "mkdir -p #{tmp_dir}/cache" | |
Dir.chdir("#{tmp_dir}/cache") do | |
run_locally "gem fetch #{name} --version '#{version}'" | |
gem_file = `ls`.chomp | |
gem_spec = YAML.load(`gem specification #{gem_file}`) | |
gem_spec.runtime_dependencies.each do |dep| | |
run_locally "gem fetch #{dep.name} --version '#{dep.requirement}'" | |
end | |
end | |
run_locally "rm -rf #{target_dir}/cache" | |
run_locally "mv #{tmp_dir}/cache #{target_dir}/" | |
puts "Cleaning up..." | |
run_locally "rm -rf #{tmp_dir}" | |
end | |
def upload_gem(name) | |
as_sudo_user do | |
vendor_path = "#{Dir.pwd}/vendor" | |
sudo "mkdir -p #{shared_path}/#{name}/cache" | |
sudo "chown -R #{user} #{shared_path}/#{name}" | |
puts Dir.glob("#{vendor_path}/#{name}/cache/*.gem") | |
Dir.glob("#{vendor_path}/#{name}/cache/*.gem").each do |path| | |
# note: do not use upload helper - upload used as task name | |
transfer :up, path, "#{shared_path}/#{name}/cache/#{File.basename(path)}" | |
end | |
end | |
end | |
def install_gem(name, version=">=0") | |
# note: run as sudo user because deploy user may not yet exist | |
as_sudo_user do | |
# if gems have been packed and uploaded, install from local gem files | |
if has_directory("#{shared_path}/#{name}/cache") | |
capture("ls -1 #{shared_path}/#{name}/cache/*.gem").split("\n").map(&:strip).each do |gemfile| | |
run "gem install #{gemfile} --version '#{version}' --no-ri --no-rdoc --local --force" | |
end | |
else | |
run "gem install #{name} --version '#{version}' --no-ri --no-rdoc" | |
end | |
end | |
end | |
def get_app_version | |
# use tagged version when deploying to master | |
# use branch name and abbreviated commit sha otherwise | |
head = `git log origin/#{branch} --abbrev-commit --oneline -n1 | cut -f1 -d' '`.chomp | |
version = `git describe origin/#{branch} --tags --match "v[0-9]*" 2> /dev/null`.chomp.sub(/^v/,'') | |
if branch == 'master' && version.length > 0 # note: allow for no tagged versions yet | |
return version | |
else | |
return "#{branch}-g#{head}" | |
end | |
end | |
def get_history | |
# TODO: raise if head not tagged | |
deploy_tags = `git tag | egrep ^#{stage}-`.split("\n") | |
changes = {} | |
previous_tag = nil | |
deploy_tags.each do |current_tag| | |
commit_range = previous_tag.nil? ? "#{current_tag}" : "#{previous_tag}..#{current_tag}" | |
changes[current_tag] = `git log #{commit_range} --no-merges --format=format:"%h %s (%an)"`.chomp | |
previous_tag = current_tag | |
end | |
return "".tap do |output| | |
changes.keys.sort.reverse.each do |tag| | |
output << "#{tag}\n" | |
output << "#{changes[tag]}\n" | |
output << "\n" | |
end | |
end | |
end | |
def get_changes | |
history_file = File.join(current_release, "HISTORY") | |
raise "History file not found, cannot get changes" unless has_file(history_file) | |
cmds = [] | |
cmds << "tail -n +2 #{history_file}" # get history file without first line/tag | |
cmds << "awk '/^#{stage}-/{exit}1'" # get changes until next tag | |
cmds << "sed '/^$/d'" # remove blank lines | |
cmds << "sed '/^#{stage}-/d'" # remove the tags | |
capture(cmds.join(" | ")).chomp | |
end | |
def get_deployer | |
if system('which git 2>&1 > /dev/null') | |
deployer = `git config user.name`.chomp | |
else | |
deployer = `whoami`.chomp | |
end | |
end |
This file contains hidden or 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
desc "Create a deployable build of the app with all dependencies" | |
task :build do | |
puts "Obtaining version info from tags..." | |
app_version = get_app_version | |
puts "Building version #{app_version}" | |
tmp_dir = "tmp/#{application}-#{app_version}" | |
puts "Cloning repository..." | |
run_locally "rm -rf #{tmp_dir}" | |
run_locally "git clone --local --no-hardlinks . #{tmp_dir}" | |
run_locally "rm -rf #{tmp_dir}/.git" | |
if ui.agree("Include bundler, passenger, rvm and ruby in build?") | |
puts "Packaging bundler..." | |
find_and_execute_task "bundler:pack" | |
run_locally "cp -Rp vendor/bundler #{tmp_dir}/vendor/" | |
puts "Packaging passenger gems..." | |
find_and_execute_task "passenger:pack" | |
run_locally "cp -Rp vendor/passenger #{tmp_dir}/vendor/" | |
puts "Packaging rvm and ruby archives..." | |
find_and_execute_task "rvm_offline:pack" | |
run_locally "cp -Rp vendor/rvm #{tmp_dir}/vendor/" | |
end | |
puts "Creating version file..." | |
run_locally "echo #{app_version} > #{tmp_dir}/VERSION" | |
puts "Creating history file..." | |
File.open("#{tmp_dir}/HISTORY", "w") { |f| f.puts get_history } | |
puts "Creating build archive..." | |
run_locally "cd tmp && tar -czf #{application}-#{app_version}.tar.gz #{application}-#{app_version}" | |
if ui.agree("Tag this build?") | |
git.prepare_tree | |
git.tag_build | |
end | |
puts "Cleaning up..." | |
run_locally "rm -rf #{tmp_dir}" | |
puts "Build created at #{Dir.pwd}/tmp/#{application}-#{app_version}.tar.gz" | |
end |
This file contains hidden or 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
namespace :bundler do | |
desc "Installs bundler gem on app servers" | |
task :install, :roles => :app do | |
install_gem :bundler | |
end | |
desc "Packages bundler gem to vendor/bundler" | |
task :pack do | |
package_gem :bundler | |
end | |
desc "Uploads packaged bundler gem to app servers" | |
task :upload, :roles => :app do | |
upload_gem :bundler | |
end | |
end |
This file contains hidden or 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 'active_support' | |
class CampfireRoom | |
def initialize(config) | |
@token = config[:token] | |
@hostname = "#{config[:account]}.campfirenow.com" | |
@room_id = get_room_id(config[:room]) | |
end | |
def speak(message) | |
send_request("https://#{@hostname}/room/#{@room_id}/speak.json", {:message => {:body => "#{message}"}}) | |
end | |
private | |
def send_request(url, content=nil) | |
curl_opts = ["--max-time 5", "--silent", "--user #{@token}:X"] | |
if content | |
curl_opts << "--header 'Content-Type: application/json'" | |
curl_opts << "--data '#{ActiveSupport::JSON.encode(content)}'" | |
end | |
response = `curl #{curl_opts.join(' ')} #{url}` | |
return ActiveSupport::JSON.decode(response) | |
end | |
def get_room_id(name) | |
room = get_rooms.select{|room| room["name"] == name}.first | |
raise "Room '#{name}' not found" if room.nil? | |
return room["id"] | |
end | |
def get_rooms | |
response = send_request("https://#{@hostname}/rooms.json") | |
rooms = response["rooms"] | |
raise "No rooms found" if rooms.nil? | |
return rooms | |
end | |
end | |
namespace :campfire do | |
namespace :deploy do | |
desc "Sends deploy notification to campfire" | |
task :notify do | |
raise "Campfire options not set" unless fetch(:campfire_options, nil) | |
begin | |
message = "[#{application}] #{get_deployer} deployed #{get_app_version} to #{stage}" | |
CampfireRoom.new(campfire_options).speak(message) | |
rescue Exception | |
puts "Campfire deploy notification failed with error #{$!}" | |
end | |
end | |
end | |
end |
This file contains hidden or 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
# variables for database.yml (TODO: handle multiple adapters/dbtypes) | |
_cset(:db_user) { "#{application}_#{stage}" } | |
_cset(:db_name) { "#{application}_#{stage}" } | |
_cset(:db_pass) { generate_password } | |
_cset(:db_host) do | |
if roles[:db].servers.size == 1 | |
"localhost" | |
else | |
# Note: assumes that caspistrano will only ever know about the master db server | |
roles[:db].servers.select{|server| server.options[:no_release] }.first.host | |
end | |
end | |
after "deploy:finalize_update", "db:yaml:symlink" | |
namespace :db do | |
namespace :yaml do | |
desc "Symlink database.yml for this release" | |
task :symlink, :roles => :app, :except => { :no_release => true } do | |
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml" | |
end | |
task :write, :except => { :no_release => true } do | |
if has_file("#{shared_path}/config/database.yml") | |
logger.important "Skipping upload of shared database.yml, destination file exists" | |
else | |
run "mkdir -p #{shared_path}/config" | |
upload_template "database.yml", "#{shared_path}/config/database.yml" | |
end | |
end | |
task :read, :roles => :db, :only => { :primary => true } do | |
content = read_file("#{shared_path}/config/database.yml") | |
config = YAML.load(content)[rails_env] | |
set :db_user, config["username"] | |
set :db_name, config["database"] | |
set :db_host, config["host"] | |
set :db_pass, config["password"] | |
end | |
task :delete, :except => { :no_release => true } do | |
run "rm -f #{shared_path}/config/database.yml" | |
end | |
end | |
end |
This file contains hidden or 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
<%= rails_env %>: | |
adapter: postgresql | |
encoding: utf8 | |
pool: 5 | |
host: <%= db_host %> | |
database: <%= db_name %> | |
username: <%= db_user %> | |
password: <%= db_pass %> |
This file contains hidden or 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
# Recipe allows you to set environment variables in capistrano | |
# and upload a .env file containing name value pairs on deploy. | |
# | |
# Set environment variables using standard capistrano variables | |
# with an dotenv_ prefix, e.g. set :dotenv_foo, "bar" | |
# | |
# Names for environment variables written to the .env file are | |
# generated by replacing the dotenv_ prefix with the application | |
# name, e.g. with app name "myapp", set(:dotenv_foo,"bar") would | |
# result in name value pair "MYAPP_FOO=bar" | |
namespace :dotenv do | |
namespace :deploy do | |
desc "Generate and upload .env file" | |
task :upload, :roles => :app do | |
# generate content for .env file | |
content = variables.keys.select{|key| | |
# only variables with env_ prefix | |
key.to_s.match(/^dotenv_/) | |
}.map{|key| | |
# generate name value pair, replacing env_ prefix | |
# with application name to generate env var name | |
name = key.to_s.sub(/^dotenv_/,"#{fetch(:application)}_").upcase | |
value = fetch(key) | |
"#{name}=#{value}" | |
}.join("\n") | |
# upload .env file to root of release path | |
ui.say "Uploading .env file..." | |
upload_content content, "#{release_path}/.env" | |
end | |
end | |
end |
This file contains hidden or 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
_cset(:duplicity_remote_host) do | |
ui.ask("Enter hostname of duplicity target:") | |
end | |
_cset(:duplicity_remote_user) do | |
ui.ask("Enter username for #{duplicity_remote_host}:") | |
end | |
_cset(:duplicity_passphrase) do | |
ui.ask("Enter GPG passphrase for symmetric encryption:") | |
end | |
_cset(:duplicity_remote_port) { 22 } | |
_cset(:duplicity_remote_path) { "duplicity" } # note: relative to default dir | |
_cset(:duplicity_local_path) { "/var/local/backup" } | |
# Use pexpect backend by default because host key checking is | |
# broken with the current version of paramiko on centos 6. | |
# Explicity set cipher algorithm to AES256 because GnuPG default | |
# is CAST5 and we don't want to modify gnupg.conf to overide it. | |
_cset(:duplicity_options) do | |
"--verbosity info --ssh-backend pexpect --gpg-options '--cipher-algo=aes256'" | |
end | |
namespace :duplicity do | |
desc "Installs duplicity and dependencies" | |
task :install do | |
yum :install, :duplicity, :sshpass, :gnupg2 | |
end | |
namespace :configure do | |
desc "Configures duplicity backups" | |
task :default do | |
check_ssh_connection | |
generate_ssh_keys | |
ssh_copy_id | |
do_test_backup | |
upload_duplicity_cron | |
end | |
task :check_ssh_connection do | |
as_sudo_user do | |
find_servers.each do |server| | |
begin | |
@ssh_password ||= ui.ask("Enter password for #{duplicity_remote_user}@#{duplicity_remote_host}: ") { |q| q.echo = false } | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
put @ssh_password, "#{tmp_file}", :hosts => server.host | |
begin | |
sudo "sshpass -f #{tmp_file} ssh #{duplicity_remote_user}@#{duplicity_remote_host} -p #{duplicity_remote_port} -o StrictHostKeyChecking=no \"echo OK\"" | |
ensure | |
sudo "rm -rf #{tmp_file}", :hosts => server.host | |
end | |
rescue Capistrano::CommandError | |
logger.important "SSH connection failed from #{server.host} to #{duplicity_remote_port}" | |
unless duplicity_remote_port.to_i == 22 | |
logger.info "Check that firewall rules allow outbound tcp traffic to #{duplicity_remote_host} on port #{duplicity_remote_port}" | |
end | |
exit 1 | |
end | |
end | |
end | |
end | |
task :generate_ssh_keys do | |
as_sudo_user do | |
find_servers.each do |server| | |
if has_file("/root/.ssh/id_rsa", :hosts => server.host, :sudo => true) | |
logger.info "Skipping key generation for root@#{server.host}, found existing RSA keys" | |
else | |
logger.important "Generating RSA keys for root@#{server.host}" | |
sudo "ssh-keygen -t rsa -N '' -f /root/.ssh/id_rsa", :hosts => server.host | |
end | |
end | |
end | |
end | |
task :ssh_copy_id do | |
as_sudo_user do | |
find_servers.each do |server| | |
begin | |
sudo "ssh #{duplicity_remote_user}@#{duplicity_remote_host} -p #{duplicity_remote_port} -o PasswordAuthentication=no -o PreferredAuthentications=publickey \"echo OK\"", :hosts => server.host | |
logger.info "Skipping ssh-copy-id for root@#{server.host}, public key authentication already configured" | |
rescue Capistrano::CommandError | |
logger.important "Copying public key to #{duplicity_remote_host}" | |
# use sshpass and scp install public key to authorized keys file on remote host | |
# Note: ssh-copy-id doesn't work with rsync.net (relies on ~ expansion which is not supported) | |
@ssh_password ||= ui.ask("Enter password for #{duplicity_remote_user}@#{duplicity_remote_host}: ") { |q| q.echo = false } | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
put @ssh_password, "#{tmp_file}", :hosts => server.host | |
begin | |
if sudo_capture("sshpass -f #{tmp_file} ssh #{duplicity_remote_user}@#{duplicity_remote_host} -p #{duplicity_remote_port} \"test -f .ssh/authorized_keys\"; echo $?", :hosts => server.host).chomp == "0" | |
# found authorized keys file on backup target, append to file | |
sudo "sshpass -f #{tmp_file} scp -P #{duplicity_remote_port} #{duplicity_remote_user}@#{duplicity_remote_host}:.ssh/authorized_keys /tmp/", :hosts => server.host | |
sudo "chown #{user} /tmp/authorized_keys" | |
sudo "cat /root/.ssh/id_rsa.pub >> /tmp/authorized_keys" | |
else | |
# no authorized keys file on backup target, create new file | |
sudo "cp /root/.ssh/id_rsa.pub /tmp/authorized_keys" | |
end | |
sudo "sshpass -f #{tmp_file} scp -P #{duplicity_remote_port} /tmp/authorized_keys #{duplicity_remote_user}@#{duplicity_remote_host}:.ssh/", :hosts => server.host | |
sudo "ssh #{duplicity_remote_user}@#{duplicity_remote_host} -p #{duplicity_remote_port} \"chmod 600 .ssh/authorized_keys; chown #{duplicity_remote_user}:#{duplicity_remote_user} .ssh/authorized_keys\"", :hosts => server.host | |
sudo "rm -rf /tmp/authorized_keys", :hosts => server.host | |
ensure | |
sudo "rm -rf #{tmp_file}", :hosts => server.host | |
end | |
end | |
end | |
end | |
end | |
task :do_test_backup do | |
as_sudo_user do | |
find_servers.each do |server| | |
local_tmp_dir = "/tmp/#{SecureRandom.hex}" | |
sftp_base_url = "sftp://#{duplicity_remote_user}@#{duplicity_remote_host}:#{duplicity_remote_port}" | |
remote_tmp_dir = "tmp/duplicity" # note: relative to default path (usually user's home directory) | |
begin | |
sudo "mkdir -p #{local_tmp_dir}" | |
sudo "touch #{local_tmp_dir}/file{1..100}.tmp" | |
options = "--no-encryption #{duplicity_options}" | |
logger.important "Creating test backup on #{duplicity_remote_host}" | |
sudo "duplicity full #{options} #{local_tmp_dir} #{sftp_base_url}/#{remote_tmp_dir}" | |
logger.important "Verifying test backup on #{duplicity_remote_host}" | |
sudo "duplicity verify #{options} #{sftp_base_url}/#{remote_tmp_dir} #{local_tmp_dir}" | |
ensure | |
sudo "rm -rf #{local_tmp_dir}" | |
sudo "ssh #{duplicity_remote_user}@#{duplicity_remote_host} -p #{duplicity_remote_port} 'rm -rf #{remote_tmp_dir}'" | |
end | |
end | |
end | |
end | |
task :upload_duplicity_cron do | |
as_sudo_user do | |
upload_template "duplicity.cron", "/etc/cron.daily/duplicity.cron", | |
:mode => "700", :owner => "root", :group => "root" | |
end | |
end | |
end | |
end |
This file contains hidden or 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
namespace :email do | |
namespace :deploy do | |
desc "Sends deploy notification to email recipients" | |
task :notify, :roles => :app, :except => { :no_release => true } do | |
raise "Email notification failed, CHANGES file not found" unless has_file("#{release_path}/CHANGES") | |
if fetch(:email_recipients, nil).nil? | |
logger.important "Email notification not sent - no recipients configured" | |
else | |
subject = "[#{application}] #{get_deployer} deployed #{get_app_version} to #{stage}" | |
Array(fetch(:email_recipients)).each do |recipient| | |
# note: to avoid send mail as application/octet-stream (see mail manpage), | |
# use sed to remove carriage returns and cat -v to print other control chars | |
run "sed 's/\\r$//' #{release_path}/CHANGES | cat -v | mail -s '#{subject}' #{recipient}", :once => true | |
end | |
end | |
end | |
end | |
end |
This file contains hidden or 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
# ABORTED GPG, STORED ONLY FOR FUTURE REFERENCE | |
# always force an export of the secret key after generating | |
after "gpg:gen_key", "gpg:export_secret_keys" | |
namespace :gpg do | |
desc "Installs GnuPG and dependencies" | |
task :install do | |
yum :install, :gpg, :haveged | |
end | |
desc "Configures GnuPG and dependencies" | |
task :configure do | |
as_sudo_user do | |
# install and run haveged to ensure sufficient entropy is available | |
# to avoid blocking IO issues with /dev/random when generating keys | |
sudo "chkconfig haveged on" | |
service :haveged, :start | |
end | |
end | |
desc "Generates key pairs on each server" | |
task :gen_key do | |
as_sudo_user do | |
# ensure gpg-agent is running so we can generate keys | |
# see http://www.netroby.com/view.php?id=3571#.U2Ndpq1dXR0 | |
find_servers.each do |server| | |
if capture("ps -A | grep gpg-agent 2> /dev/null | cat", :hosts => server.host).chomp == "" | |
sudo "gpg-agent --daemon --use-standard-socket" | |
end | |
hostname = capture("hostname", :hosts => server.host).chomp | |
logger.log Capistrano::Logger::IMPORTANT, "Generating GPG keys for #{hostname}" | |
gpg_key_name = hostname | |
if sudo_capture("gpg --list-secret-keys root@#{hostname} 2> /dev/null | cat", :hosts => server.host).chomp != "" | |
logger.log Capistrano::Logger::IMPORTANT, "WARNING: Existing private key found matching root@#{hostname}" | |
logger.info "Skipping GPG key generation for root@#{hostname}" | |
next | |
end | |
# generate params file for unattended key generation | |
# see https://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html | |
content = %{ | |
%echo Generating key for #{hostname} | |
Key-Type: default | |
Subkey-Type: default | |
Name-Real: #{hostname} | |
Name-Email: root@#{hostname} | |
Expire-Date: 0 | |
# Do a commit here, so that we can later print "done" :-) | |
%commit | |
%echo done | |
} | |
tmp_file = "/tmp/#{SecureRandom.hex}" | |
put content, "#{tmp_file}", :hosts => server.host | |
begin | |
sudo "gpg --batch --gen-key #{tmp_file}", :hosts => server.host | |
ensure | |
sudo "rm -rf #{tmp_file}", :hosts => server.host | |
end | |
end | |
end | |
end | |
desc "Exports secret keys from each server" | |
task :export_secret_keys do | |
as_sudo_user do | |
find_servers.each do |server| | |
hostname = capture("hostname", :hosts => server.host).chomp | |
logger.log Capistrano::Logger::IMPORTANT, "Exporting secret keys from #{hostname}" | |
content = sudo_capture("gpg --armour --export-secret-keys") | |
filename = "#{hostname}.gpg.asc" | |
path = File.join(Dir.pwd, filename) | |
File.open(path, "w"){|f| f.puts content} | |
logger.log Capistrano::Logger::IMPORTANT, "Export saved as #{path}" | |
logger.info "Move this export file to a secure backup location!" | |
end | |
end | |
end | |
end |
This file contains hidden or 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
<%= deploy_to %>/shared/log/*.log { | |
daily | |
rotate 7 | |
delaycompress | |
missingok | |
notifempty | |
sharedscripts | |
postrotate | |
/sbin/service httpd reload > /dev/null 2>/dev/null || true | |
endscript | |
} |
This file contains hidden or 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
after "deploy:setup", "logrotation:deploy:setup" | |
namespace :logrotation do | |
namespace :deploy do | |
desc "Generates logrotate configuration for app" | |
task :setup, :roles => :app do | |
as_sudo_user do | |
destination = "/etc/logrotate.d/#{application}_#{stage}" | |
upload_template "logrotate", destination, :sudo => true | |
end | |
end | |
end | |
end |
This file contains hidden or 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
after "mod_security:install", "apache:reload" | |
after "mod_security:remove", "apache:reload" | |
after "mod_security:configure", "apache:reload" | |
namespace :mod_security do | |
desc "Installs mod_security apache module using yum" | |
task :install, :roles => :web do | |
yum :install, :mod_security, :mod_security_crs | |
end | |
task :remove, :roles => :web do | |
yum :remove, :mod_security | |
end | |
task :reinstall, :roles => :web do | |
yum :reinstall, :mod_security, :mod_security_crs | |
end | |
desc "Configures mod_security apache module" | |
task :configure, :roles => :web do | |
as_sudo_user do | |
# Remove rulesets we don't want (TODO: don't be so brutal) | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_20_protocol_violations.conf" | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_21_protocol_anomalies.conf" | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_30_http_policy.conf" | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_sql_injection_attacks.conf" | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_41_xss_attacks.conf" | |
sudo "rm -f /etc/httpd/modsecurity.d/activated_rules/modsecurity_crs_50_outbound.conf" | |
end | |
end | |
end |
This file contains hidden or 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
LoadModule passenger_module <%= passenger_root %>/ext/apache2/mod_passenger.so | |
PassengerRoot <%= passenger_root %> | |
PassengerRuby <%= passenger_ruby %> | |
PassengerFriendlyErrorPages off | |
Header always unset "X-Powered-By" | |
Header always unset "X-Runtime" |
This file contains hidden or 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
after "passenger:configure", "apache:reload" | |
_cset(:passenger_version) { "~>3" } | |
namespace :passenger do | |
desc "Installs passenger module for apache" | |
task :install, :roles => :web do | |
# note: run as sudo user because deploy user may not yet exist | |
as_sudo_user do | |
install_gem :passenger, fetch(:passenger_version) | |
run "passenger-install-apache2-module --auto" | |
end | |
end | |
desc "Configures passenger module for apache" | |
task :configure, :roles => :web do | |
rvm_home = "/usr/local/rvm" | |
gem_home = "#{rvm_home}/gems/ruby-#{rvm_ruby_string}" | |
as_sudo_user do | |
# note: deploy user may not yet exist | |
passenger_gem = capture("ls -1 #{gem_home}/gems | grep passenger").split("\n").last.chomp | |
set :passenger_root, gem_home + "/gems/" + passenger_gem | |
set :passenger_ruby, "#{rvm_home}/wrappers/ruby-#{rvm_ruby_string}/ruby" | |
upload_template "passenger.conf", "/etc/httpd/conf.d/passenger.conf", :sudo => true | |
copy_permissions("/etc/httpd/conf/httpd.conf", "/etc/httpd/conf.d/passenger.conf") | |
end | |
end | |
desc "Packages passenger gem and dependencies to vendor/passenger" | |
task :pack do | |
package_gem :passenger, fetch(:passenger_version) | |
end | |
desc "Uploads packaged passenger gem and dependencies to servers" | |
task :upload, :roles => :web do | |
upload_gem :passenger | |
end | |
end |
This file contains hidden or 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
after "postfix:configure", "postfix:restart" | |
_cset(:postconf_inet_protocols) { "ipv4" } | |
_cset(:postconf_relayhost) { nil } | |
namespace :postfix do | |
desc "Installs postfix using yum" | |
task :install, :roles => :app do | |
yum :install, :postfix | |
end | |
desc "Restarts postfix service" | |
task :restart, :roles => :app do | |
service :postfix, :restart | |
end | |
namespace "configure" do | |
desc "Configures postfix server using postconf utility" | |
task :default do | |
start_on_boot | |
update_configuration | |
end | |
task "start_on_boot", :roles => :app do | |
as_sudo_user do | |
sudo "chkconfig postfix on" | |
end | |
end | |
desc "Updates postfix configuration using postconf utility" | |
task :update_configuration, :roles => :app do | |
config_file = "/etc/postfix/main.cf" | |
tmp_file = "/tmp/main.cf" | |
as_sudo_user do | |
keep_original config_file | |
variables.keys.select{|k| k.to_s.match(/^postconf_/) }.each do |key| | |
sudo "postconf -e \"#{key.to_s.sub('postconf_', '')}=#{fetch(key)}\"" | |
end | |
end | |
end | |
end | |
end |
This file contains hidden or 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
# When installing on some RHEL servers, we ran into an issue with the loading | |
# of shared libraries. Though the postgresql installation had placed a config | |
# file in /etc/ld.so.conf.d/, the configuration didn't seem to reload and it | |
# was necessary to run ldconfig manually to ensure shared libs would be found. | |
after "postgres:install" do | |
as_sudo_user do | |
sudo "ldconfig" | |
end | |
end | |
after "postgres:configure", "postgres:restart" | |
after "postgres:setup", "postgres:restart" | |
namespace :postgres do | |
namespace :install do | |
desc "Installs postgres client and server using yum" | |
task :default do | |
add_yum_repo | |
install_client | |
install_libs | |
install_server | |
symlink_pg_config | |
initdb | |
end | |
task :add_yum_repo do | |
# TODO: exclude postgresql from CentOS/RHEL base and updates repos | |
as_sudo_user do | |
# TODO: run on each server individually - tho unlikely, | |
# servers could in theory run different distros | |
if has_file("/etc/centos-release") | |
package = "pgdg-centos92-9.2-6.noarch.rpm" | |
else | |
package = "pgdg-redhat92-9.2-6.noarch.rpm" | |
end | |
# note: rpm exits with non-zero status if package already installed | |
sudo "rpm -ivh http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/#{package} ; echo ''" | |
end | |
end | |
task :install_client, :roles => [:app, :db] do | |
yum :install, :postgresql92 | |
end | |
task :install_libs, :roles => [:app, :db] do | |
yum :install, "postgresql92-devel" | |
end | |
task :install_server, :roles => :db, :only => { :no_release => true } do | |
yum :install, "postgresql92-server", "postgresql92-contrib" | |
end | |
task :symlink_pg_config, :roles => :app do | |
# pg gem needs to run pg_config, so just symlink it on the app servers. | |
# Note: when installed via yum, not all postgres binaries are available | |
# in the user's path, as only those that can be used with multiple | |
# versions are symlinked. See the following blog post for an explanation: | |
# http://people.planetpostgresql.org/devrim/index.php?/archives/43-How-to-install-PostgreSQL-9.0-Beta-1-to-FedoraCentOSRHEL.html | |
as_sudo_user do | |
if capture("which pg_config > /dev/null ; echo $?").chomp == "0" | |
logger.important "Warning: pg_config found in path, symlink not created" | |
else | |
sudo "ln -s /usr/pgsql-9.2/bin/pg_config /usr/bin/pg_config" | |
end | |
end | |
end | |
task :initdb, :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
sudo "service postgresql-9.2 initdb" | |
end | |
end | |
end | |
desc "Restarts postgresql-9.2 service" | |
task :restart, :roles => :db, :only => { :no_release => true } do | |
service "postgresql-9.2", :restart | |
end | |
namespace :configure do | |
desc "Configures postgres server" | |
task :default do | |
start_on_boot | |
upload_backup_cron | |
upload_maintenance_cron | |
upload_auth_config | |
configure_listen_addresses | |
end | |
task "start_on_boot", :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
sudo "chkconfig postgresql-9.2 on" | |
end | |
end | |
task :upload_backup_cron, :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
upload_resource "postgres/postgresql-backup.cron", | |
"/etc/cron.daily/postgresql-backup.cron", | |
:mode => "755", :owner => "root", :group => "root" | |
end | |
end | |
task :upload_maintenance_cron, :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
upload_resource "postgres/postgresql-maintenance.cron", | |
"/etc/cron.weekly/postgresql-maintenance.cron", | |
:mode => "755", :owner => "root", :group => "root" | |
end | |
end | |
task :upload_auth_config, :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
config_file = "/var/lib/pgsql/9.2/data/pg_hba.conf" | |
keep_original config_file | |
upload_resource "postgres/pg_hba.conf", config_file, :sudo => true | |
copy_original_permissions config_file | |
end | |
end | |
task :configure_listen_addresses, :roles => :db, :only => { :no_release => true } do | |
puts "DB host is #{fetch(:db_host)}" | |
if %w{ 127.0.0.1 localhost }.include?(fetch(:db_host)) | |
# FIXME: assumes default configuration setting has not been modified | |
puts "Nothing to do, postgres listens on localhost by default" | |
else | |
# FIXME: brittle, relies on exact string match to modify listen addresses | |
puts "Configuring postgres to listen on all addresses..." | |
as_sudo_user do | |
config_file = "/var/lib/pgsql/9.2/data/postgresql.conf" | |
tmp_file = "/tmp/postgresql.conf" | |
sudo "sed \"s/#listen_addresses = 'localhost'/listen_addresses = '\\*'/\" #{config_file} > #{tmp_file}" | |
keep_original config_file | |
sudo "mv #{tmp_file} #{config_file}" | |
copy_original_permissions config_file | |
end | |
end | |
end | |
end | |
namespace :setup do | |
desc "Prepares postgres for deployments in general" | |
task :default do | |
create_deploy_user | |
configure_auth | |
configure_client | |
end | |
task :create_deploy_user, :roles => :db, :only => { :no_release => true } do | |
deploy_user = user | |
as_sudo_user do | |
# note: no password set here - see task to prepare psql client connection | |
sudo "su - postgres -c \"createuser #{deploy_user}\"; true" # return true to allow for fail if user exists | |
# deploy user needs to be superuser in order to create extensions | |
sudo "su - postgres -c \"psql -d template1 -c 'ALTER USER #{deploy_user} WITH SUPERUSER;'\"" | |
end | |
end | |
# note: this task assumes that the remote address for each of the app servers, | |
# as seen from the database server, will match the host or IP specified when | |
# configuring the role or server in capistrano. | |
task :configure_auth, :roles => :db, :only => { :no_release => true } do | |
as_sudo_user do | |
config_file = "/var/lib/pgsql/9.2/data/pg_hba.conf" | |
tmp_file = "/tmp/pg_hba.conf" | |
keep_original config_file | |
roles[:app].servers.map(&:to_s).each do |host| | |
address = host.match(/^(\d+\.){3}\d+$/) ? "#{host}/32" : host | |
# remove existing host lines matching this host | |
# this is little heavy handed, but it's easier than grepping to check | |
# for existing line due to permissions issues when using capture helper | |
sudo "sed '/^host .* #{host}/d' #{config_file} > #{tmp_file}" | |
# and allow md5 auth from this host | |
line = "host all all #{address} md5" | |
run "echo \"#{line}\" >> #{tmp_file}" | |
sudo "mv #{tmp_file} #{config_file}" | |
copy_original_permissions config_file | |
end | |
end | |
end | |
task :configure_client, :roles => :db do | |
deploy_user = user | |
password = generate_password | |
# set the password on the db server | |
as_sudo_user do | |
sudo "su - postgres -c \"psql -d template1 -c \\\"ALTER USER #{deploy_user} PASSWORD '#{password}';\\\"\"", :only => { :no_release => true } | |
end | |
# create a .pgpass file for the client connection | |
run "echo \"*:*:*:#{user}:#{password}\" > ~/.pgpass", :only => { :primary => true } | |
run "chmod 600 /home/#{user}/.pgpass", :only => { :primary => true } | |
end | |
end | |
desc "Checks postgres client connection" | |
task :check, :roles => :db, :only => { :primary => true } do | |
run "psql -d template1 -h #{db_host} -c \"select 'foo'\"" | |
end | |
namespace :deploy do | |
namespace :setup do | |
desc "Prepares postgres for a specific deploy target" | |
task :default do | |
db.yaml.write | |
create_database | |
grant_privileges | |
end | |
desc "[internal] Creates the database if it doesn't exist" | |
task :create_database, :roles => :db, :only => { :no_release => true } do | |
db.yaml.read | |
if capture("psql -d template1 -c \"SELECT datname FROM pg_database;\"").lines.grep(/#{db_name}/).empty? | |
run "psql -d template1 -c \"CREATE DATABASE #{db_name} ENCODING 'UTF-8';\"" | |
else | |
logger.important "Warning: database not created because it already exists" | |
end | |
end | |
desc "[internal] Grants db user privileges on database" | |
task "grant_privileges", :roles => :db, :only => { :no_release => true } do | |
db.yaml.read | |
if capture("psql -d template1 -c \"SELECT usename FROM pg_user;\"").lines.grep(/#{db_user}/).empty? | |
run "psql -d template1 -c \"CREATE USER #{db_user} PASSWORD '#{db_pass}';\"" | |
else | |
run "psql -d template1 -c \"ALTER USER #{db_user} PASSWORD '#{db_pass}';\"" | |
end | |
run "psql -d template1 -c \"GRANT ALL PRIVILEGES ON DATABASE #{db_name} TO #{db_user};\"" | |
run "psql -d #{db_name} -c \"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO #{db_user};\"" | |
run "psql -d template1 -c \"ALTER DATABASE #{db_name} OWNER TO #{db_user};\"" | |
end | |
end | |
end | |
end |
This file contains hidden or 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
after "release_files:deploy:upload", "release_files:deploy:verify" | |
namespace :release_files do | |
namespace :deploy do | |
desc "Generate and upload release files" | |
task :upload do | |
# Version and history files can only be created when deploying via git repo. | |
# When deploying via copy, they should already exist in the deployable build. | |
put get_app_version, "#{release_path}/VERSION" unless fetch(:deploy_via) == :copy | |
put get_history, "#{release_path}/HISTORY" unless fetch(:deploy_via) == :copy | |
put get_changes, "#{release_path}/CHANGES" | |
end | |
desc "Verifies that release files exist on remote server" | |
task :verify, :roles => :app do | |
%w{ VERSION HISTORY CHANGES }.each do |file| | |
raise "#{file} file not found" unless has_file("#{release_path}/#{file}") | |
end | |
end | |
end | |
end |
This file contains hidden or 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
# set configuration variables before requiring rvm-capistrano | |
set :rvm_type, :system # system install for multiple rvm users | |
set :rvm_install_with_sudo, true # needed for system install | |
# disable setting rvm_require_role - rvm-capistrano sets the default shell | |
# to the rvm shell for all servers, not just those matching rvm_require_role. | |
# TODO: update rvm-capistrano if fixed in later version, otherwise report bug | |
# set :rvm_require_role, :app # only install rvm to app servers | |
require 'rvm/capistrano' | |
# run rvm tasks as the sudo user | |
# FIXME: switch the user for the duration of the task | |
namespaces[:rvm].task_list(:all).map(&:fully_qualified_name).each do |task_name| | |
before(task_name) { set :user, sudo_user } | |
end | |
# ensure sudo user is added to rvm group | |
# note: account:setup sets group membership for deploy user | |
after "rvm:install_rvm" do | |
sudo "usermod -a -G rvm #{user}", :shell => rvm_install_shell | |
end | |
# install required packages before installing rvm | |
before "rvm:install_rvm", "rvm:install_pkgs" | |
# require rvm-capistrano-offline if found in gem bundle and add callbacks | |
if Bundler.locked_gems.dependencies.map(&:name).include?("rvm-capistrano-offline") | |
require 'rvm/capistrano/offline' | |
# run rvm-offline tasks as sudo user | |
namespaces[:rvm_offline].task_list(:all).map(&:fully_qualified_name).each do |task_name| | |
next if task_name == "rvm_offline:pack" | |
before(task_name) { set :user, sudo_user } | |
end | |
# ensure sudo user is added to rvm group | |
after "rvm_offline:install" do | |
sudo "usermod -a -G rvm #{user}", :shell => rvm_install_shell | |
end | |
# install packages before installing rvm via rvm-offline | |
before "rvm_offline:install", "rvm:install_pkgs" | |
end | |
namespace :rvm do | |
# FIXME: should only run on servers matching rvm_require_role | |
task :install_pkgs do | |
packages = %w{ | |
gcc gcc-c++ glibc patch readline readline-devel zlib | |
zlib-devel libyaml-devel libffi-devel openssl-devel | |
make bzip2 autoconf automake libtool bison curl-devel | |
libxslt-devel libxml2-devel | |
} | |
yum :install, *packages | |
end | |
desc "Reinstall RVM ruby on the server" | |
task :reinstall_ruby do | |
set :rvm_install_ruby, :reinstall | |
install_ruby | |
end | |
end |
This file contains hidden or 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
<VirtualHost *:443> | |
ServerName <%= apache_server_name %> | |
RedirectPermanent / http://<%= apache_server_name %>/ | |
Include ssl/<%= application %>.conf | |
</VirtualHost> | |
<VirtualHost *:80> | |
ServerName <%= apache_server_name %> | |
DocumentRoot <%= deploy_to %>/current/public | |
PassengerMinInstances 1 | |
RailsEnv <%= rails_env %> | |
<Directory <%= deploy_to %>/current/public> | |
AllowOverride all | |
Options -MultiViews | |
</Directory> | |
# Show maintenance page if it exists | |
ErrorDocument 503 /system/maintenance.html | |
RewriteEngine On | |
RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$ | |
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f | |
RewriteCond %{SCRIPT_FILENAME} !maintenance.html | |
RewriteRule ^.*$ - [redirect=503,last] | |
</VirtualHost> |
This file contains hidden or 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
<VirtualHost *:80> | |
ServerName <%= apache_server_name %> | |
RedirectPermanent / https://<%= apache_server_name %>/ | |
</VirtualHost> | |
<VirtualHost *:443> | |
ServerName <%= apache_server_name %> | |
DocumentRoot <%= deploy_to %>/current/public | |
PassengerMinInstances 1 | |
RailsEnv <%= rails_env %> | |
<Directory <%= deploy_to %>/current/public> | |
AllowOverride all | |
Options -MultiViews | |
</Directory> | |
Include ssl/<%= application %>.conf | |
# Show maintenance page if it exists | |
ErrorDocument 503 /system/maintenance.html | |
RewriteEngine On | |
RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$ | |
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f | |
RewriteCond %{SCRIPT_FILENAME} !maintenance.html | |
RewriteRule ^.*$ - [redirect=503,last] | |
</VirtualHost> |
This file contains hidden or 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
_cset(:yum_cron_check_only) { "yes" } | |
_cset(:yum_cron_download_only) { "no" } | |
_cset(:yum_cron_debug_level) { 1 } | |
_cset(:yum_cron_randomwait) { 15 } | |
_cset(:yum_cron_yum_parameter) { ""} | |
_cset(:yum_cron_mailto) do | |
ui.ask("Enter email address for yum-cron notifications:") | |
end | |
namespace :yum_cron do | |
desc "Installs yum-cron" | |
task :install do | |
yum :install, "yum-cron" | |
end | |
desc "Configures yum-cron" | |
task :configure do | |
as_sudo_user do | |
# start on boot | |
sudo "chkconfig yum-cron on" | |
# modify configuration settings | |
variables.keys.select{|k| k.to_s.match(/^yum_cron_/) }.each do |key| | |
param = key.to_s.sub('yum_cron_', '').upcase | |
value = fetch(key) | |
update_configuration("/etc/sysconfig/yum-cron", param, value) | |
end | |
# start service | |
service "yum-cron", :start | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment