diff --git a/Gemfile b/Gemfile index 93f7c561f..1c8f3d417 100644 --- a/Gemfile +++ b/Gemfile @@ -64,6 +64,7 @@ group :development do # gem 'capistrano-exts', :require => false #, :git => 'git://github.com/chipiga/capistrano-exts.git' # gem 'capistrano-recipes', :require => false gem 'capistrano_colors', :require => false + gem 'bluepill', :require => false end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 7b5fcce74..33a001a38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,6 +41,11 @@ GEM activerecord (>= 2.2.2) arel (2.0.10) bcrypt-ruby (3.0.1) + bluepill (0.0.51) + activesupport (>= 3.0.0) + daemons (~> 1.1.0) + i18n (>= 0.5.0) + state_machine (~> 0.9.4) builder (2.1.2) cancan (1.6.7) cape (1.0.1) @@ -180,6 +185,7 @@ GEM i18n (>= 0.5.0) sexp_processor (3.0.8) silent-postgres (0.1.1) + state_machine (0.9.4) thor (0.14.6) treetop (1.4.10) polyglot @@ -204,6 +210,7 @@ PLATFORMS DEPENDENCIES airbrake (~> 3.0.5) ancestry (~> 1.2.4) + bluepill cancan (~> 1.6.7) cape capistrano diff --git a/app/controllers/platforms_controller.rb b/app/controllers/platforms_controller.rb index dc9fe0bbb..6cd21c217 100644 --- a/app/controllers/platforms_controller.rb +++ b/app/controllers/platforms_controller.rb @@ -55,8 +55,7 @@ class PlatformsController < ApplicationController @platform.owner = (params[:admin_uname]) ? User.find_by_uname(params[:admin_uname]) : nil @platform.owner ||= get_owner - if @platform.save! -# @platform.make_admin_relation(@platform.owner.id) + if @platform.save flash[:notice] = I18n.t("flash.platform.saved") redirect_to @platform else diff --git a/app/models/platform.rb b/app/models/platform.rb index 375789fc2..e54d85280 100644 --- a/app/models/platform.rb +++ b/app/models/platform.rb @@ -60,6 +60,10 @@ class Platform < ActiveRecord::Base build_path(name) end + def mount_path + Rails.root.join("public", "downloads", name) + end + def hidden? visibility == 'hidden' end @@ -106,31 +110,20 @@ class Platform < ActiveRecord::Base end def mount_directory_for_rsync - #FileUtils.rm_rf "#{ Rails.root.join('tmp', 'umount', self.name) }" if File.exist? "#{ Rails.root.join('tmp', 'umount', name) }" - #FileUtils.mkdir_p "#{ Rails.root.join('tmp', 'mount', name) }" - system("sudo mkdir -p #{ Rails.root.join("public", "downloads") }/#{ name }") - system("sudo mount --bind /home/share/platforms/#{ name } #{ Rails.root.join("public", "downloads") }/#{ name }") - #system("sudo cp -f /srv/rosa_build/current/tmp/mount/#{ name }/* /home/share/platforms/#{ name }/repository/") - #system("sudo rm -Rf \"/srv/rosa_build/current/tmp/mount/#{ name }\"") + # umount_directory_for_rsync # TODO ignore errors + system("sudo mkdir -p #{mount_path}") + system("sudo mount --bind #{path} #{mount_path}") Arch.all.each do |arch| host = EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host] url = "http://#{host}/downloads/#{name}/repository/" str = "country=Russian Federation,city=Moscow,latitude=52.18,longitude=48.88,bw=1GB,version=2011,arch=#{arch.name},type=distrib,url=#{url}\n" - File.open(Rails.root.join("public", 'downloads', name, "#{name}.#{arch.name}.list"), 'w') {|f| f.write(str) } + File.open(File.join(mount_path, "#{name}.#{arch.name}.list"), 'w') {|f| f.write(str) } end end def umount_directory_for_rsync - system("sudo umount #{ Rails.root.join("public", "downloads") }/#{ name }") - system("sudo rm -Rf #{ Rails.root.join("public", "downloads") }/#{ name }") - #system("rm -Rf \"/srv/rosa_build/current/tmp/umount/#{ name }\"") - #FileUtils.rm_rf "#{ Rails.root.join('tmp', 'mount', name) }" if File.exist? "#{ Rails.root.join('tmp', 'mount', name) }" - #FileUtils.mkdir_p "#{ Rails.root.join('tmp', 'umount', name) }" - end - - def make_admin_relation(user_id) - r = self.relations.build :object_id => user_id, :object_type => 'User', :role => 'admin' - r.save + system("sudo umount #{mount_path}") + system("sudo rm -Rf #{mount_path}") end protected diff --git a/bin/autostart.sh b/bin/autostart.sh new file mode 100755 index 000000000..7877066ac --- /dev/null +++ b/bin/autostart.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +for f in `ls /srv` +do + sudo -u rosa rvmsudo bundle exec bluepill load /srv/$f/current/config/production.pill APP_NAME=$f +done + +/srv/rosa_build/current/bin/mount_downloads.sh diff --git a/bin/mount_downloads.sh b/bin/mount_downloads.sh index d1b639089..6bb68f669 100755 --- a/bin/mount_downloads.sh +++ b/bin/mount_downloads.sh @@ -2,8 +2,9 @@ for f in `ls /srv/rosa_build/shared/downloads` do - if [ -d "/home/share/platforms/$f" ] + if [ -d /home/share/platforms/$f ] then - mount --bind "/home/share/platforms/$f" "/srv/rosa_build/shared/downloads/$f" + sudo umount /srv/rosa_build/shared/downloads/$f 2>&1 >> /dev/null + sudo mount --bind /home/share/platforms/$f /srv/rosa_build/shared/downloads/$f fi done diff --git a/bin/mount_rsync.sh b/bin/mount_rsync.sh deleted file mode 100755 index 3075bc908..000000000 --- a/bin/mount_rsync.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -for f in `ls /srv/rosa_build/current/tmp/mount` -do - mkdir -p "/srv/rosa_build/shared/downloads/$f" - mount --bind "/home/share/platforms/$f" "/srv/rosa_build/shared/downloads/$f" - cp -f /srv/rosa_build/current/tmp/mount/$f/* /home/share/platforms/$f/repository/ - rm -Rf "/srv/rosa_build/current/tmp/mount/$f" -done diff --git a/bin/rotate_nginx.sh b/bin/rotate_nginx.sh deleted file mode 100755 index f3518c6bb..000000000 --- a/bin/rotate_nginx.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -mv /srv/rosa_build/shared/log/nginx.access.log /srv/rosa_build/shared/log/nginx.access.log.0 -/etc/init.d/nginx reload -chown rosa /srv/rosa_build/shared/log/nginx.access.log.0 -# touch /home/rosa/i_was_launched.txt diff --git a/bin/umount_rsync.sh b/bin/umount_rsync.sh deleted file mode 100755 index e70d48795..000000000 --- a/bin/umount_rsync.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -for f in `ls /srv/rosa_build/current/tmp/umount` -do - umount "/srv/rosa_build/shared/downloads/$f" - rm -Rf "/srv/rosa_build/shared/downloads/$f" - rm -Rf "/srv/rosa_build/current/tmp/umount/$f" -done diff --git a/config/deploy.rb b/config/deploy.rb index 806df8fc1..70a7f949b 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -29,6 +29,7 @@ set :scm, "git" require 'lib/recipes/nginx' require 'lib/recipes/unicorn' +require 'lib/recipes/bluepill' namespace :deploy do # task :restart, :roles => :app, :except => { :no_release => true } do # run "touch #{current_release}/tmp/restart.txt" @@ -50,16 +51,11 @@ namespace :deploy do run "mkdir -p #{fetch :shared_path}/downloads" run "ln -nfs #{fetch :shared_path}/downloads/ #{fetch :release_path}/public/downloads" - - run "mkdir -p #{fetch :shared_path}/tmp" - run "ln -nfs #{fetch :shared_path}/pids/ #{fetch :shared_path}/tmp/pids" - run "ln -nfs #{fetch :shared_path}/tmp/ #{fetch :release_path}/tmp" - run "mkdir -p #{fetch :release_path}/tmp/mount" - run "mkdir -p #{fetch :release_path}/tmp/umount" end end after "deploy:update_code", "deploy:symlink_all", "deploy:migrate" +after "deploy:update", "bluepill:quit", "bluepill:start" after "deploy:restart", "delayed_job:restart", "deploy:cleanup" require 'cape' diff --git a/config/production.pill b/config/production.pill new file mode 100644 index 000000000..ecdda48e4 --- /dev/null +++ b/config/production.pill @@ -0,0 +1,34 @@ +#! /usr/bin/env ruby + +app_name = ENV['APP_NAME'] rescue 'rosa_build' +Bluepill.application(app_name) do |app| + app.uid = app.gid = 'rosa' + app.working_dir = "/srv/#{app_name}/current" + app.process("delayed_job") do |process| + process.start_grace_time = 10.seconds + process.stop_grace_time = 10.seconds + process.restart_grace_time = 10.seconds + + process.start_command = "bundle exec script/delayed_job -e production start" + process.stop_command = "bundle exec script/delayed_job -e production stop" + process.pid_file = File.join('tmp', 'pids', 'delayed_job.pid') + end + + app.process("unicorn") do |process| + process.start_grace_time = 8.seconds + process.stop_grace_time = 5.seconds + process.restart_grace_time = 13.seconds + + process.start_command = "bundle exec unicorn -l /tmp/#{app_name}_unicorn.sock -c config/unicorn.rb -D" + process.stop_command = "kill -QUIT {{PID}}" + process.restart_command = "kill -USR2 {{PID}}" + process.pid_file = File.join('tmp', 'pids', 'unicorn.pid') + + process.monitor_children do |child_process| + child_process.stop_command = "kill -QUIT {{PID}}" + + child_process.checks :mem_usage, :every => 10.seconds, :below => 150.megabytes, :times => [3,4], :fires => :stop + child_process.checks :cpu_usage, :every => 10.seconds, :below => 20, :times => [3,4], :fires => :stop + end + end +end diff --git a/config/unicorn.rb b/config/unicorn.rb index 75611debd..44ee663a5 100644 --- a/config/unicorn.rb +++ b/config/unicorn.rb @@ -1,11 +1,10 @@ +base_path = File.expand_path(File.join File.dirname(__FILE__), '..') rails_env = ENV['RAILS_ENV'] || 'production' + worker_processes 4 +working_directory base_path # available in 0.94.0+ -preload_app true - -working_directory File.expand_path(File.join(File.dirname(__FILE__), "..")) # available in 0.94.0+ - -# listen '/tmp/rosa_build.sock', :backlog => 2048 +# listen File.join(base_path, 'tmp', 'pids', 'unicorn.sock') # listen "/tmp/.sock", :backlog => 64 # listen 8080, :tcp_nopush => true @@ -13,7 +12,7 @@ working_directory File.expand_path(File.join(File.dirname(__FILE__), "..")) # a timeout 600 # feel free to point this anywhere accessible on the filesystem -pid File.expand_path(File.join(File.dirname(__FILE__), "..")) + '/tmp/pids/unicorn.pid' +pid File.join(base_path, 'tmp', 'pids', 'unicorn.pid') # REE # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow @@ -24,8 +23,8 @@ end # By default, the Unicorn logger will write to stderr. # Additionally, ome applications/frameworks log to stderr or stdout, # so prevent them from going to /dev/null when daemonized here: -stderr_path File.expand_path(File.join(File.dirname(__FILE__), "..")) + "/log/unicorn.stderr.log" -stdout_path File.expand_path(File.join(File.dirname(__FILE__), "..")) + "/log/unicorn.stdout.log" +stderr_path File.join(base_path, 'log', 'unicorn.stderr.log') +stdout_path File.join(base_path, 'log', 'unicorn.stdout.log') # combine REE with "preload_app true" for memory savings # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow @@ -42,7 +41,7 @@ before_fork do |server, worker| # we send it a QUIT. # # Using this method we get 0 downtime deploys. - old_pid = File.expand_path(File.join(File.dirname(__FILE__), "..")) + '/tmp/pids/unicorn.pid.oldbin' + old_pid = File.join(base_path, 'tmp', 'pids', '/unicorn.pid.oldbin') if File.exists?(old_pid) && server.pid != old_pid begin Process.kill("QUIT", File.read(old_pid).to_i) diff --git a/lib/recipes/bluepill.rb b/lib/recipes/bluepill.rb new file mode 100644 index 000000000..43e4c78cf --- /dev/null +++ b/lib/recipes/bluepill.rb @@ -0,0 +1,21 @@ +Capistrano::Configuration.instance(:must_exist).load do + namespace :bluepill do + set(:bluepill_config) { File.join release_path, 'config', 'production.pill' } + + desc "Stop processes that bluepill is monitoring and quit bluepill" + task :quit, :roles => [:app] do + sudo "bundle exec bluepill stop APP_NAME=#{fetch :application}" + sudo "bundle exec bluepill quit APP_NAME=#{fetch :application}" + end + + desc "Load bluepill configuration and start it" + task :start, :roles => [:app] do + sudo "bundle exec bluepill load #{bluepill_config} APP_NAME=#{fetch :application}" + end + + desc "Prints bluepills monitored processes statuses" + task :status, :roles => [:app] do + sudo "bundle exec bluepill status APP_NAME=#{fetch :application}" + end + end +end diff --git a/lib/recipes/nginx.rb b/lib/recipes/nginx.rb index f5907946f..c4ab59ad6 100644 --- a/lib/recipes/nginx.rb +++ b/lib/recipes/nginx.rb @@ -8,7 +8,8 @@ Capistrano::Configuration.instance(:must_exist).load do task :generate_configuration, :roles => :web, :except => { :no_release => true } do config = %Q{ upstream #{application}_backend { - server 127.0.0.1:#{unicorn_port}; + # server 127.0.0.1:#{unicorn_port}; + server unix:/tmp/#{fetch :application}_unicorn.sock; } server { diff --git a/lib/recipes/unicorn.rb b/lib/recipes/unicorn.rb index d313dd942..ce7ec5f94 100644 --- a/lib/recipes/unicorn.rb +++ b/lib/recipes/unicorn.rb @@ -1,12 +1,11 @@ Capistrano::Configuration.instance(:must_exist).load do namespace :deploy do set :unicorn_binary, "bundle exec unicorn" - set(:unicorn_config) { "#{fetch :current_path}/config/unicorn.rb" } set(:unicorn_pid) { "#{fetch :shared_path}/tmp/pids/unicorn.pid" } - set :unicorn_port, 8080 + # set :unicorn_port, 8080 task :start, :roles => :app, :except => { :no_release => true } do - run "cd #{fetch :current_path} && #{try_sudo} #{unicorn_binary} -c #{unicorn_config} -p #{unicorn_port} -E #{rails_env} -D" + run "cd #{fetch :current_path} && #{try_sudo} #{unicorn_binary} -l /tmp/#{fetch :application}_unicorn.sock -E #{rails_env} -c config/unicorn.rb -D" # -p #{unicorn_port} end task :stop, :roles => :app, :except => { :no_release => true } do run "#{try_sudo} kill `cat #{unicorn_pid}`" rescue warn 'deploy:stop FAILED'