## ./Rakefile
require 'vlad'
Vlad.load :scm => :git, :app => 'postfix', :core => 'syscore', :web => nil, :config => 'vlad/deploy.rb'
## ./vlad/syscore.rb
# This is a copy of 'core.rb' in vlad that doesn't make the assumption we're running a webapp, and therefore doesn't try and do symlinks for /logs etc.
require 'vlad'
# used by update, out here so we can ensure all threads have the same value
def now
@now ||= Time.now.utc.strftime("%Y%m%d%H%M.%S")
end
namespace :vlad do
desc "Show the vlad setup. This is all the default variables for vlad
tasks.".cleanup
task :debug do
require 'yaml'
# force them into values
Rake::RemoteTask.env.keys.each do |key|
next if key =~ /_release|releases|sudo_password/
Rake::RemoteTask.fetch key
end
puts "# Environment:"
puts
y Rake::RemoteTask.env
puts "# Roles:"
y Rake::RemoteTask.roles
end
desc "Setup your servers. Before you can use any of the deployment
tasks with your project, you will need to make sure all of your
servers have been prepared with 'rake vlad: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.".cleanup
task :setup do
Rake::Task['vlad:setup_app'].invoke
end
desc "Updates your application server to the latest revision. Syncs
a copy of the repository, exports it as the latest release, fixes
up your symlinks, symlinks the latest revision to current and logs
the update.".cleanup
remote_task :update, :roles => :app do
symlink = false
rev = revision rescue 'head' # If revision doesn't exist, default to 'head'
begin
run [ "cd #{scm_path}",
"#{source.checkout rev, '.'}",
"#{source.export ".", release_path}",
"chmod -R g+w #{latest_release}",
].join(" && ")
symlink = true
run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
run "echo #{now} $USER #{rev} #{File.basename release_path} >> #{deploy_to}/revisions.log"
rescue => e
run "rm -f #{current_path} && ln -s #{previous_release} #{current_path}" if
symlink
run "rm -rf #{release_path}"
raise e
end
end
desc "Invoke a single command on every remote server. This is useful for
performing one-off commands that may not require a full task to be written
for them. Simply specify the command to execute via the COMMAND
environment variable. To execute the command only on certain roles,
specify the ROLES environment variable as a comma-delimited list of role
names.
$ rake vlad:invoke COMMAND='uptime'".cleanup
remote_task :invoke do
command = ENV["COMMAND"]
abort "Please specify a command to execute on the remote servers (via the COMMAND environment variable)" unless command
puts run(command)
end
desc "Copy arbitrary files to the currently deployed version using
FILES=a,b,c. This is useful for updating files piecemeal when you
need to quickly deploy only a single file.
To use this task, specify the files and directories you want to copy as a
comma-delimited list in the FILES environment variable. All directories
will be processed recursively, with all files being pushed to the
deployment servers. Any file or directory starting with a '.' character
will be ignored.
$ rake vlad:upload FILES=templates,controller.rb".cleanup
remote_task :upload do
file_list = (ENV["FILES"] || "").split(",")
files = file_list.map do |f|
f = f.strip
File.directory?(f) ? Dir["#{f}/**/*"] : f
end.flatten
files = files.reject { |f| File.directory?(f) || File.basename(f)[0] == ?. }
abort "Please specify at least one file to update (via the FILES environment variable)" if files.empty?
files.each do |file|
rsync file, File.join(current_path, file)
end
end
desc "Rolls back to a previous version and restarts. This is handy if you
ever discover that you've deployed a lemon; 'rake vlad:rollback' and
you're right back where you were, on the previously deployed
version.".cleanup
remote_task :rollback do
if releases.length < 2 then
abort "could not rollback the code because there is no prior release"
else
run "rm #{current_path}; ln -s #{previous_release} #{current_path} && rm -rf #{current_release}"
end
Rake::Task['vlad:start'].invoke
end
desc "Clean up old releases. By default, the last 5 releases are kept on
each server (though you can change this with the keep_releases variable).
All other deployed revisions are removed from the servers.".cleanup
remote_task :cleanup do
count = keep_releases
old_releases = (releases - releases.last(count))
if count >= releases.size
# if old_releases.empty? then
puts "no old releases to clean up"
else
puts "keeping #{count} of #{releases.length} deployed releases"
directories = (releases - releases.last(count)).map { |release|
File.join(releases_path, release)
}.join(" ")
run "rm -rf #{directories}"
end
end
end # namespace vlad
## ./vlad/postfix.rb
# the actual tasks for postfix
namespace :vlad do
# for whatever reason, Rake::RemoteTask#sudo isn't actually running for me. Grr.
def sudo(command)
run [sudo_cmd, sudo_flags, command].join(' ')
end
set :postfix_command, '/usr/sbin/postfix'
set :postfix_init, '/etc/init.d/postfix'
set :postmap_command, '/usr/sbin/postmap'
desc "Prepare the remote server for deployment"
remote_task :setup_app do
dirs = [deploy_to, releases_path, scm_path, shared_path]
# run "sudo umask 02"
sudo "mkdir -p #{dirs.join(' ')}"
sudo "chown -R $USER #{deploy_to}"
end
desc "symlinks the deployment location to /etc"
remote_task :update_symlink do
sudo "ln -nfs #{current_path} #{etc_path}"
end
desc "compiles the hashmap files"
remote_task :compile_maps do
sudo "#{postmap_command} #{etc_path}/*.maps"
end
desc "sets owner of the symlink and the deploy location to root so postfix doesn't get cranky"
remote_task :set_perms => :compile_maps do
# sudo "#{postfix_command} set-permissions"
sudo "chown -R root:root #{current_release}"
sudo "chown -R root:root #{etc_path}"
sudo "chmod -R 644 #{current_path}"
sudo "chmod 755 #{current_path}/postfix-script"
sudo "chown -R $USER:$USER #{previous_release}"
end
desc "Checks the main and master .cf files have ok syntax. Does NOT check that map files are compiled."
remote_task :check_configuration => :set_perms do
sudo "#{postfix_command} check"
end
desc "Reloads the Postfix configuration."
remote_task :reload_configuration do
sudo "#{postfix_init} reload"
end
desc "Reloads or Starts Postfix as necessary"
remote_task :restart_app do
# how do we determine if postfix is running?
end
desc "Stops the Postfix server."
remote_task :stop_app do
sudo "#{postfix_init} stop"
end
desc "Starts the Postfix server."
remote_task :start_app do
sudo "#{postfix_init} start"
end
end
## ./vlad/deploy.rb
#IMO this should be default; it looks for /^Password:/
set :sudo_flags, "-p Password:"
set :application, "postfix"
set :domain, 'mattly'
host domain, :app
# where the scm checkouts will live
set :deploy_to, '/var/apps/postfix'
# where the system expects the files to be
set :etc_path, '/etc/postfix'
set :repository, 'git@mattly:postfix.git'
namespace :vlad do
desc "Get the shit onto the server and tell postfix about it."
task :deploy => [:update, :update_symlink, :check_configuration, :reload_configuration]
end