namespace :website do namespace :backends do # Allow for defining tasks named `test' and `try': class << self undef_method :test if private_method_defined? :test undef_method :try if public_method_defined? :try end task(:default) { list } import_pack(:connect, self, "backend") desc <<-DESC List all the backends currently running the site. DESC task :list do site = website.fetch_current! puts_title "Backends of website: #{site}" show_list(site.backends.all) end desc <<-DESC Prepare a VPS for hosting the current website. So is done by creating a dedicated UNIX user to run the site as, enclosing processes related to the site in a tight, secure environment. This prevents potential security flaws to have an impact on other services run within the same VPS. Environment variables: (optional) $ON: hostname of the VPS. Default: last webserver set up. (optional) $AS: UNIX account to run the services as. Default: the name of the website. DESC task :add do site = website.fetch_current! unless webserver = (ENV["ON"] ? (vps = VPS.find(ENV["ON"])).webservers : Webserver).last message = vps ? "No webserver seems to be running on VPS #{q vps}." : "You have no webserver set up yet." message << " Set one up first by running #{qcommand "webserver:setup"}." error! message end # Backend: ties together the site to the webserver backend = site.backends.build(:webserver => webserver, :user => ENV["AS"]) try_save backend do puts_title "Request sent" puts_long <<-INFO * A new backend for website #{q site} has been scheduled for setup on VPS #{q backend.vps}. * If you are using an automatically generated Capfile, you should download an updated version with #{qcommand "website:capfile:download"}. * Once the new backend is ready (check that #{qcommand "progress"} is at 100%), you should re-deploy to push the site to it. If you are using a Capfile, this is done with #{q "cap deploy:setup"} followed by #{q "cap deploy"}. INFO end end desc <<-DESC Force a re-configuration of a backend. Checks the UNIX user, re-generates configuration files and reloads the webserver's configuration if necessary. Environment variables: (optional) $ID: the ID of a particular backend (get the list with #{qcommand "website:backends:list"}). Default: all backends of the current websites are scheduled for reconfiguration. DESC task :reconfigure do backends = fetch_several puts_title "Sending requests" backends.each do |backend| backend.put(:reconfigure) puts "Backend #{backend} has been scheduled for reconfiguration." end end desc <<-DESC Force a re-preparation of a backend. This re-runs the whole preparation process that was performed at addition time. It's necessary to perform the preparation again when switching application types of a website. For example: $ cd path/to/foo $ #{command "website:apptype:switch"} ID=custom $ #{command "website:backends:reprepare"} DESC task :reprepare do backends = fetch_several puts_title "Sending requests" backends.each do |backend| backend.put(:reprepare) puts "Backend #{backend} has been scheduled for re-preparation." end end task :test do backend = fetch_current execute = lambda { |command| top.webserver.connect.ssh_details_for(webserver).execute(command) } # Check for errors during installations puts_title "Installations" none, display = true, lambda do |error, script_name| begin error = backend.send(error) puts_subtitle "Error while executing #{q script_name}" puts error.output rescue WebService::ResourceNotFound # ignore else none = false end end display[:root_install_error, 'root_install.sh'] display[:install_error, 'install.sh'] puts "No error occured during installations." if none # Check syntax of webserver configuration file puts_title "Webserver configuration" webserver = backend.webserver command = "/usr/sbin/nginx -c $HOME/webserver/nginx_main.conf -t" top.webserver.connect.ssh_details_for(webserver).execute(command) # Fetch the home page locally puts_title "Home page (beginning)" command = "wget -q -O - localhost:#{backend.port} | head -15" top.website.backends.connect.ssh_details_for(backend).execute(command) end desc <<-DESC Detect errors on service startup. Try to run the service script (*_start.sh of the app. type) like the service manager would, in order to spot errors output during startup, that would otherwise land in an error log. Eases the task of debugging why an application progress won't start like when forgetting to install a necessary RubyGem. Environment variables: (optional) $ID: the ID of the backend to try to start the service within. Default: the backend last added. DESC task :try do backend = fetch_current if type = backend.website.app_type type.service_configs.each do |service| run = lambda { |cmd| connect.ssh_details_for(backend).execute(cmd) } # Display service status dir = "$HOME/service/#{backend.website.name}-#{service.name}-1" run["sv status #{dir}"] # Try to start the service in the foreground to spot errors on startup command = "#{dir}/run" command = "p=$$; ((sleep 2; kill $p) &); #{command}" # timeout run[command] end end end desc <<-DESC Stop a backend and clear site-related files in a soft way. All the files related to the associated website are not deleted, but instead moved in a backup directory in the home folder of the backends's UNIX account. This UNIX account is then soft-cleared in a similar fashion. That is, it is unregistered from the system, and its files are moved to a backup directory under /home. Example backup directory for a backend running as "foo": /home/_foo Example backup directory for a website named "bar" on this backend: /home/_foo/_bar That way, the VPS is kept in a clean state, and your files are still available for you to get them back if you need them. This would most likely be the case if the site accepts uploads stored on the filesystem, in a subdirectory of the site's root, like RAILS_ROOT/public/images/ for a Rails application, or wp-content/ for a WordPress blog. Environment variables: $ID: the ID of the backend to remove. DESC task :remove do id = require_id! website = top.website.fetch_current! backend = website.backends.find(id) if website.backends.all.size <= 1 confirm <<-WARN You are about to remove the only backend left for this website. If you do proceed with the removal, the site will become offline until you set up a new one with #{qcommand "website:backends:add"} and re-deploy. WARN end backend.destroy puts_title "Request sent" puts_long <<-INFO * Backend #{backend} has been scheduled for removal. * If you are using the automatically generated Capfile, you should download the updated version with #{qcommand "website:capfile:download"}. INFO end def require_id! ENV["ID"] or error! <<-MSG The ID of the backend to deal with must be specified by setting the $ID environment variable. To list all backends of the current website, run #{qcommand "website:backends"}. MSG end def fetch_current! id = require_id! website.fetch_current!.backends.find(id) end def fetch_current backends = website.fetch_current!.backends (id = ENV["ID"]) ? backends.find(id) : backends.last end def fetch_several backends = website.fetch_current!.backends (id = ENV["ID"]) ? [backends.find(id)] : backends.all end def show_list(backends) puts_list backends do |t| t.col("Website") { |b| b.website.name } t.col("Running on", :vps) t.col("Running as", :user) t.col("Instances", :num_instances) t.col("Internal web port", :port) end end end end