Skip to content

Instantly share code, notes, and snippets.

@code
Created December 1, 2008 04:26
Show Gist options
  • Save code/30620 to your computer and use it in GitHub Desktop.
Save code/30620 to your computer and use it in GitHub Desktop.
# File: dreamhost_agent.rb
# Author: Luke Hubbard
# Gist: http://gist.github.com/gists/30620
# License: MIT
require 'rubygems'
require 'rubygists'
require 'net/ssh'
require 'net/scp'
require 'logger'
gist_require 21814 # dreamhost_adapter.rb
gist_require 21812 # password_generator.rb
class DreamhostAgent
LIBS = %w{
http://kernel.org/pub/software/scm/git/git-1.6.0.3.tar.gz
ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p72.tar.gz
ftp://ftp.gnu.org/gnu/readline/readline-5.2.tar.gz
http://www.openssl.org/source/openssl-0.9.8i.tar.gz
http://rubyforge.iasi.roedu.net/files/rubygems/rubygems-1.3.1.tgz
}
DEFAULT_GEMS = %w{rails haml json mysql rack rspec hpricot capistrano capistrano-ext}
DEFAULT_OPTIONS = {
:shell=>'bash',
:lockdown_homedir=>true,
:disable_ftp=>true,
:sa_chk=>true,
:notify_disk=>true,
:quote_chk=>true,
:hard_quota=>(1024*100).to_s, # 100 GB
:app_path=>"app",
:mod_security_on=>true,
:passenger=>true,
:fastcgi=>false,
:www_redir=>"both",
:google_apps=>false,
:gmail=>false,
:gems=>DEFAULT_GEMS
}
REQUIRED_PARAMS = [:dh_user, :dh_password, :shell_user, :shell_password, :domain, :db_user, :db_prefix, :db_password]
NAME_SERVER = 'ns1.dreamhost.com'
attr_accessor :log, :options, :ssh, :scp, :api
def initialize(options={})
self.log ||= Logger.new(STDOUT, :level=>:debug)
self.options = DEFAULT_OPTIONS.merge(options)
REQUIRED_PARAMS.each {|param| log.warn("Missing required param: #{param}") if options[param].nil? }
end
def [](name)
raise("Missing required param: #{name}") unless options.has_key?(name)
self.options[name]
end
def api
@api ||= DreamhostAdapter.new(self[:dh_user], self[:dh_password])
end
def ssh
@ssh ||= Net::SSH.start(ip(self[:domain]), self[:shell_user], :password => self[:shell_password])
end
def scp
@scp ||= Net::SCP.start(ip(self[:domain]), self[:shell_user], :password => self[:shell_password])
end
def put(file, data)
scp.upload! StringIO.new(data), file
end
def ip(domain, ns=NAME_SERVER)
dig = `dig @#{ns} #{domain} | grep #{domain}`.split("\n").last
dig =~ /IN\s+A\s+([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/ ? $1 : nil
end
def setup_user
api.add_user!(
:user=>self[:shell_user], #
:password=>self[:shell_password], #
:type=>'shell',
:shell=>self[:shell],
:lockdown_homedir=>self[:lockdown_homedir],
:disable_ftp=>self[:disable_ftp],
:sa_chk=>self[:sa_chk],
:notify_disk=>self[:notify_disk],
:quote_chk=>self[:quote_chk],
:hard_quota=>self[:hard_quota]
)
log.warn("It could take some time for this user to show up, wait 5 mins.")
end
def setup_domain
api.add_domain!(
:domain=>self[:domain], #
:user=>self[:shell_user], #
:relpath=>self[:app_path]+"/current/public",
:mod_security_on=>self[:mod_security_on],
:passenger=>self[:passenger],
:fastcgi=>self[:fastcgi],
:www_redir=>self[:www_redir],
:google_apps=>self[:google_apps],
:gmail=>self[:gmail]
)
end
def setup_database
api.add_mysql!(
:dbname=>self[:db_prefix]+'_development', #
:user=>self[:db_user], #
:password=>self[:db_password] #
)
api.add_mysql!(
:dbname=>self[:db_prefix]+'_test', #
:user=>self[:db_user], #
:password=>self[:db_password] #
)
api.add_mysql!(
:dbname=>self[:db_prefix]+'_staging', #
:user=>self[:db_user], #
:password=>self[:db_password] #
)
api.add_mysql!(
:dbname=>self[:db_prefix]+'_production', #
:user=>self[:db_user], #
:password=>self[:db_password] #
)
end
def download_libs
exec!("[ -d ~/tmp ] || (mkdir -p ~/tmp && echo 'Created temp dir ~/tmp')")
exec!('cd ~/tmp && '+LIBS.collect { |lib| "curl #{lib} | tar zxv" }.join('; '))
end
def setup_dotfiles
put("~/.bash_profile", <<-EOF
EDITOR="/usr/bin/nano -w"
export PS1="\\[\\e]0;\\w\\a\\]\\n\\[\\e[32m\\]\\u@\\h \\[\\e[33m\\]\\w\\[\\e[0m\\]\\n\\\$ "
export LS_COLORS="no=00:fi=00:di=01;36:ln=01;35:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:"
export LSCOLORS='GxFxcxdxCxegedabagacad'
# GENERAL
alias df='df -h'
alias du='du -h'
alias grep='grep --color'
alias ls='ls --color'
alias ll='ls -l'
. .bashrc
EOF
)
put("~/.bashrc", <<-EOF
export TZ="Europe/London"
export LD_LIBRARY_PATH="$HOME/.packages/lib"
export PATH="$HOME/.packages/bin:$HOME/.gems/bin:${PATH}"
export GEM_HOME="$HOME/.gems"
export GEM_PATH="$GEM_HOME:/usr/lib/ruby/gems/1.8"
EOF
)
exec!("mkdir -p ~/.ssh")
put("~/.ssh/authorized_keys", `cat ~/.ssh/id_rsa.pub`)
exec!("chmod -R 700 ~/.ssh")
# force relogin
close
end
def close
@scp = nil unless @scp.nil?
@ssh = nil unless @ssh.nil?
end
def exec!(cmd)
log.info cmd
result = ssh.exec!(cmd)
log.info result
result
end
def setup_libs
exec!(%{mkdir -p ~/.packages})
exec!(%{cd ~/tmp/git-* && ./configure --prefix=$HOME/.packages NO_CURL=1 NO_MMAP=1 && make && make install})
exec!(%{cd ~/tmp/readline-* && ./configure --prefix=$HOME/.packages && make && make install})
exec!(%{cd ~/tmp/openssl-* && ./config --prefix=$HOME/.packages --openssldir=$HOME/openssl shared && make && make install})
exec!(%{cd ~/tmp/ruby-* && ./configure --prefix=$HOME/.packages --with-openssl-dir=$HOME/.packages --with-readline-dir=$HOME/.packages && make && make install})
exec!(%{cd ~/tmp/rubygems-* && ruby setup.rb config --prefix=$HOME/.packages && ruby setup.rb setup && ruby setup.rb install rubygems})
end
def install_gems
exec!(%{gem install #{self[:gems].collect{|it| it.to_s}.join(' ')} --no-rdoc --no-ri})
end
def prepare_capistrano
exec!(%{cd ~/#{self[:app_path]} && rm -rdf current && mkdir -p shared/config && mkdir -p shared/log && mkdir -p shared/pids && mkdir -p shared/system && mkdir -p releases})
config = <<-CONFIG
default_run_options[:pty] = true
# Application
set :application, "" # THIS IS THE ONLY VARIABLE YOU MUST SET YOURSELF
# Connection
set :user, "#{self[:shell_user]}"
set :ssh_options, { :forward_agent => true } # You need to run 'ssh-add' before calling 'cap'
set :domain, "#{ip(self[:domain])}" # Using the IP instead of host incase of DNS issues.
# set :gems_for_project, %w(dr_nic_magic_models swiftiply) # list of gems to be installed
set :use_sudo, false
# Deployment
set :scm, "git"
set :scm_user, "#{self[:git_user]}"
set :repository, lambda { "[email protected]:#{self[:git_user]}/\#{application}.git" }
set :deploy_to, "/home/slice299/app" # fixed location for deployment
set :branch, "master"
set :git_enable_submodules, 1
set :deploy_via, :remote_cache
# Database
set :db_prefix, "#{self[:db_prefix]}"
set :db_user, "#{self[:db_user]}"
set :db_password, "#{self[:db_password]}"
set :db_host, "#{self[:db_host]}"
# Update these if you're not running everything on one host.
role :app, domain
role :web, domain
role :db, domain, :primary => true
#############################################################
# Passenger
#############################################################
namespace :mod_rails do
desc <<-DESC
Restart the application altering tmp/restart.txt for mod_rails.
DESC
task :restart, :roles => :app do
run "touch \#{current_path}/tmp/restart.txt"
end
end
namespace :deploy do
%w(start restart).each { |name| task name, :roles => :app do mod_rails.restart end }
end
task :setup, :roles => [:app, :db, :web] do
desc "A setup task to put shared system, log, and database directories in place"
run <<-CMD
mkdir -p -m 775 \#{release_path} \#{shared_path}/system \#{shared_path}/db &&
mkdir -p -m 777 \#{shared_path}/log \#{shared_path}/public/images/avatar
CMD
end
before "deploy:setup", :db
after "deploy:update_code", "db:symlink"
namespace :db do
desc "Create database yaml in shared path"
task :default do
db_config = ERB.new <<-EOF
base: &base
host: \#{db_host}
adapter: mysql
username: \#{db_user}
password: \#{db_password}
development:
database: \#{db_prefix}_development
<<: *base
test:
database: \#{db_prefix}_test
<<: *base
staging:
database: \#{db_prefix}_staging
<<: *base
production:
database: \#{db_prefix}_production
<<: *base
EOF
run "mkdir -p \#{shared_path}/config"
put db_config.result, "\#{shared_path}/config/database.yml"
end
desc "Make symlink for database yaml"
task :symlink do
run "ln -nfs \#{shared_path}/config/database.yml \#{release_path}/config/database.yml"
end
end
CONFIG
puts config
end
def setup!
log.info "Setting up user account"
setup_user
# to be safe we should wait 5-10 mins here.
log.info("Waiting for 5 mins for user to show up.. Go make coffee!")
sleep(60*5)
log.info "Adding domain to dreamhost"
setup_domain
log.info "Waiting for 2 mins for DNS to update."
sleep(60*2)
log.info "Adding database to #{self[:db_host]}"
setup_database
log.info "Adding dotfiles .bash_profile .bash_rc and ssh public key"
setup_dotfiles
log.info "Downloading packages for compilation"
download_libs
log.info "Compiling packages"
setup_libs
log.info "Installing gems"
install_gems
log.info "Preparing for capistrano"
prepare_capistrano
log.info "Install done! Wow that took a while."
puts options.inspect
puts ip(self[:domain])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment