352 lines
17 KiB
Ruby
352 lines
17 KiB
Ruby
require 'net/ssh'
|
|
require 'pathname'
|
|
require 'json'
|
|
require 'base64'
|
|
namespace :exec_commands do
|
|
desc "Exec commands Script"
|
|
task :exec_commands,[:base64_args,:site_construct_id,:commands,:type,:server_name,:rails_env,:commands_i18n] => :environment do |task,args|
|
|
if args.base64_args.present?
|
|
tmp = JSON.parse(Base64.decode64(args.base64_args)) rescue {}
|
|
args = Rake::TaskArguments.new(tmp.keys, tmp.values)
|
|
end
|
|
@type = args.type
|
|
if args.site_construct_id.present?
|
|
@site_construct = SiteConstruct.find(args.site_construct_id)
|
|
site_server = SiteServer.where(:server_name=>@site_construct.server_type).first
|
|
site_servers = [site_server]
|
|
@site_construct.update(:status=>"execing",:infos=>[""])
|
|
else
|
|
@site_construct = nil
|
|
site_servers = SiteServer.where(:server_name.in=>args.server_name.split("////")).to_a
|
|
end
|
|
if args.type == "exec_all"
|
|
Multithread.where(:key=>'execing_commands').each do |thread|
|
|
if thread.status["status"] == "error" || thread.status["status"] == "finish"
|
|
thread.destroy
|
|
elsif thread.updated_at < (Time.now - 5.minute)
|
|
thread.destroy
|
|
end
|
|
end
|
|
@thread = Multithread.where(:key=>'execing_commands').first
|
|
else
|
|
@thread = nil
|
|
end
|
|
if @thread.nil?
|
|
@command_trans = {}
|
|
@command_relations = {"upgrade_site"=>'git fetch origin && rails runner "a=Admin::SitesController.new;a.git_reset(%w(origin)[0],%w(update)[0]);while(1) do (Multithread.where(key: %w(update_manager)[0],status: %w(finish)[0]).count!=0 ? break : nil) end;sleep(5)"',
|
|
"bundle_update"=>"bundle update && kill -s USR2 `cat tmp/pids/unicorn.pid`",
|
|
"restart_site"=>"kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock 2>/dev/null`; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E {{rails_env}}",
|
|
"get_template"=>"mongo --eval 'db.sites.find().pretty()[0][\"template\"]' {{db_name}} --quiet"}
|
|
@preserve_output = ["get_template"]
|
|
@command_relations.each do |k,v|
|
|
@command_trans[k] = I18n.t("client_management.#{k}")
|
|
end
|
|
rails_envs = ["production", "development"]
|
|
rails_envs.each do |env|
|
|
@command_relations["start_site_in_env,#{env}"] = "kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock 2>/dev/null`; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E #{env}"
|
|
@command_relations["start_site_in_env\\,#{env}"] = @command_relations["start_site_in_env,#{env}"]
|
|
@command_trans["start_site_in_env,#{env}"] = I18n.t("client_management.start_site_in_env",{:env=>env})
|
|
@command_trans["start_site_in_env\\,#{env}"] = @command_trans["start_site_in_env,#{env}"]
|
|
end
|
|
begin
|
|
if args.type == "exec_all"
|
|
if @thread.nil?
|
|
@thread = Multithread.create(:key=>'execing_commands',:status=>{"infos"=>[],"status"=>"execing"})
|
|
else
|
|
@thread.update(:status=>{"infos"=>[],"status"=>"execing"})
|
|
end
|
|
end
|
|
site_servers.each do |site_server|
|
|
ip = site_server.ip
|
|
server_port = site_server.port
|
|
user = site_server.account
|
|
password = site_server.password
|
|
@password = password
|
|
begin
|
|
Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
|
|
end
|
|
rescue Net::SSH::HostKeyMismatch
|
|
system("ssh-keygen -f \"$HOME/.ssh/known_hosts\" -R #{ip}")
|
|
rescue Errno::ENOTTY
|
|
system("ssh-add \"$HOME/.ssh/id_rsa\"")
|
|
end
|
|
Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
|
|
@site_construct.update!(:infos=>[]) rescue nil
|
|
if args.type == 'close_site'
|
|
exec_ssh_command_by_sudo_and_see_output(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
|
|
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'kill -s TERM `fuser #{@site_construct.path}/#{@site_construct.site_name}/tmp/unicorn.sock 2>/dev/null`'",false)
|
|
update_infos_for_exec("Finish closing #{@site_construct.site_name}")
|
|
@site_construct.update(:status =>"closed")
|
|
elsif args.type == 'open_site'
|
|
default_rails_env = 'development'
|
|
enable_rails_env = ['development','production']
|
|
rails_env = @site_construct.rails_env.blank? ? default_rails_env : @site_construct.rails_env
|
|
cmd_output = exec_ssh_command_by_sudo_and_see_output(ssh,"ps -o args= -p `cat #{@site_construct.path}/#{@site_construct.site_name}/tmp/pids/unicorn.pid`",false) rescue []
|
|
enable_rails_env.each do |env|
|
|
if( cmd_output[0].include?(env) rescue false)
|
|
rails_env = env
|
|
break
|
|
elsif( cmd_output[0].include?("No such file or directory") rescue false)
|
|
@site_construct.update(:status => "closed")
|
|
break
|
|
end
|
|
end
|
|
if args.rails_env.present?
|
|
rails_env = args.rails_env
|
|
end
|
|
@site_construct.update(:rails_env=>rails_env)
|
|
update_infos_for_exec("Starting site to #{rails_env}")
|
|
outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"sudo -p 'sudo password:' chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
|
|
output = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}\nkill -s TERM `fuser tmp/unicorn.sock 2>/dev/null`\nsudo -p \"sudo password:\" kill -s TERM `sudo -p \"sudo password:\" fuser tmp/unicorn.sock 2>/dev/null`\nsudo -p \"sudo password:\" rm -f tmp/pids/unicorn.pid\n rvm use `cat .ruby-version`\nbundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}\n'",false)
|
|
if output.include? "bundle install"
|
|
update_infos_for_exec("Not yet bundle install")
|
|
outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}; rvm use `cat .ruby-version`; bundle install'")
|
|
if outputs.join("\n").include? "Username for"
|
|
update_infos_for_exec("Cannot finish bundle install.")
|
|
update_infos_for_exec("This site include an private git project.")
|
|
@site_construct.update(:status =>"error")
|
|
break
|
|
else
|
|
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}'",false)
|
|
end
|
|
end
|
|
update_infos_for_exec("Finish starting #{@site_construct.site_name}")
|
|
@site_construct.update(:status =>"finish")
|
|
else
|
|
if args.type == 'exec_all'
|
|
sites = SiteConstruct.where(:server_type=>site_server.server_name).to_a
|
|
else
|
|
sites = [@site_construct]
|
|
end
|
|
commands = args.commands
|
|
if commands.class == String
|
|
commands = commands.split("////").select{|c| c != ""}
|
|
elsif commands.nil?
|
|
commands = []
|
|
end
|
|
if commands.include?("{{create_users}}")
|
|
commands = ['sudo -p "sudo password:" chmod 777 {{full_site_path}} -R',
|
|
'sudo -p "sudo password:" chmod 777 {{full_site_path}}/public -R',
|
|
'sudo -p "sudo password:" chmod 777 {{full_site_path}}/app/templates -R',
|
|
'sudo -p "sudo password:" useradd --home-dir {{full_site_path}}/app/templates {{site_name}} --no-create-home --badnames',
|
|
'sudo -p "sudo password:" bash -l -c \'echo -e "{{site_name}}\n{{site_name}}"|passwd {{site_name}}\'']
|
|
end
|
|
@commands_i18n = args.commands_i18n # command 客製化翻譯
|
|
sites.each do |site_construct|
|
|
@site_construct = site_construct
|
|
@site_construct.update!(:infos=>[])
|
|
exec_ssh_command_by_sudo(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R")
|
|
rails_env = @site_construct.rails_env.blank? ? "development" : @site_construct.rails_env
|
|
commands.each_with_index do |command, idx|
|
|
@command_i18n = command
|
|
update_flag = 1
|
|
@command_relations.each do |k,v|
|
|
if command.include?("{{#{k}}}")
|
|
if @preserve_output.include?(k)
|
|
update_flag = 1
|
|
else
|
|
update_flag = 2 #Not logging commands result
|
|
end
|
|
end
|
|
command = command.gsub("{{#{k}}}",v)
|
|
@command_i18n = @command_i18n.gsub("{{#{k}}}",@command_trans[k])
|
|
end
|
|
if @commands_i18n
|
|
tmp = @commands_i18n[idx]
|
|
@command_i18n = tmp if tmp
|
|
end
|
|
command = command.gsub("{{rails_env}}",rails_env)
|
|
command = command.gsub("{{full_site_path}}",@site_construct.full_site_path)
|
|
command = command.gsub("{{site_name}}",@site_construct.site_name)
|
|
command = command.gsub("{{db_name}}",@site_construct.db_name)
|
|
@command_i18n = @command_i18n.gsub("{{rails_env}}",rails_env)
|
|
@command_i18n = @command_i18n.gsub("{{full_site_path}}",@site_construct.full_site_path)
|
|
@command_i18n = @command_i18n.gsub("{{site_name}}",@site_construct.site_name)
|
|
command = command.gsub("'","'\"'\"'")
|
|
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name};#{command}'", update_flag)
|
|
@command_i18n = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if !@thread.nil?
|
|
@thread.update(:status=>@thread.status.merge({"status"=>"finish"}))
|
|
elsif @site_construct
|
|
@site_construct.update(:status =>"finish")
|
|
end
|
|
rescue => e
|
|
if !@thread.nil?
|
|
@thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.message),"status"=>"error"})
|
|
@thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.backtrace.join("\n")),"status"=>"error"})
|
|
end
|
|
if !@site_construct.nil?
|
|
@site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.message}"))
|
|
@site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.backtrace.join("\n")}"))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
def exec_ssh_command_by_sudo(session,command)
|
|
output = session.exec!("echo '#{@password}' | sudo -S #{command}")
|
|
# output = session.exec!("echo '#{@password}' | sudo -S -s #{command}")
|
|
if output.include?("sudo:") && output.include?("command not found")
|
|
output = session.exec!(command)
|
|
end
|
|
return output.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
|
|
end
|
|
def update_infos_for_exec(info,update_last=false,update_array=false)
|
|
return if @site_construct.nil?
|
|
if update_last && !@site_construct.infos.empty?
|
|
if @remove_last_line
|
|
@site_construct.infos[-1] = info.to_s
|
|
else
|
|
@site_construct.infos[-1] += info.to_s
|
|
end
|
|
else
|
|
if @remove_last_line
|
|
@site_construct.infos = @site_construct.infos[0...-1]
|
|
end
|
|
if update_array
|
|
@site_construct.infos += info
|
|
else
|
|
@site_construct.infos.push(info.to_s)
|
|
end
|
|
end
|
|
@site_construct.save!
|
|
return @site_construct.infos
|
|
end
|
|
def update_thread_infos_for_exec(info,update_last=false,update_array=false)
|
|
if update_last && !@thread.status["infos"].empty?
|
|
if @remove_last_line
|
|
@thread.status["infos"][-1] = info.to_s
|
|
else
|
|
@thread.status["infos"][-1] += info.to_s
|
|
end
|
|
else
|
|
if @remove_last_line
|
|
@thread.status["infos"] = @thread.status["infos"][0...-1]
|
|
end
|
|
if update_array
|
|
@thread.status["infos"] += info
|
|
else
|
|
@thread.status["infos"].push(info.to_s)
|
|
end
|
|
end
|
|
@thread.save!
|
|
return @thread.status["infos"]
|
|
end
|
|
def exec_ssh_command_and_see_output(session,command,update_flag=1,output_string=false)
|
|
outputs = []
|
|
@flag = (@type == "exec_all")
|
|
command_i18n = command
|
|
update = (update_flag == true || update_flag == 1 || update_flag == 2)
|
|
update_outputs = (update_flag == true || update_flag == 1)
|
|
if update
|
|
command_i18n = @command_i18n if @command_i18n
|
|
if @site_construct
|
|
update_thread_infos_for_exec(["Execing #{command_i18n} on on <a href='#{((@site_construct.get_port == "443") ? "https" : "http")}://#{@site_construct.domain_name}#{((@site_construct.get_port=="80" || @site_construct.get_port=="443" || @site_construct.get_port.blank?) ? "" : (':'+@site_construct.get_port))}'>#{@site_construct.domain_name}</a>",""],false,true) if @flag
|
|
update_infos_for_exec(["Execing #{command_i18n}",""],false,true)
|
|
elsif @thread
|
|
update_thread_infos_for_exec(["Execing #{command_i18n}...",""],false,true)
|
|
end
|
|
end
|
|
session.open_channel do |channel|
|
|
channel.request_pty do |channel, success|
|
|
channel.exec("LANG=en.UTF8 #{command}") do |ch, success|
|
|
abort "could not execute command: #{command}" unless success
|
|
channel.on_data do |ch, data|
|
|
data_str = data.to_s
|
|
data_str.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
|
|
if data_str.include?("sudo password:") || data_str.include?("Password:")
|
|
channel.send_data "#{@password}\n"
|
|
else
|
|
print data_str unless @no_stdout
|
|
data_str.gsub!("\r\n", "\n")
|
|
rm_idx = data_str.index("\r")
|
|
if rm_idx.nil?
|
|
@remove_last_line = false
|
|
else
|
|
@remove_last_line = (outputs.count > 0 && data_str[0...rm_idx].exclude?("\n"))
|
|
end
|
|
data_str.gsub!(/.*\r/, '')
|
|
next if data_str.length == 0
|
|
if data_str.include?("\n") || outputs.empty?
|
|
output_lines = data_str.split("\n")
|
|
first_output = output_lines[0]
|
|
if first_output
|
|
if @remove_last_line
|
|
outputs = outputs[1..-1]
|
|
outputs = [] if outputs.nil?
|
|
end
|
|
if outputs.count != 0
|
|
outputs[-1] += first_output
|
|
else
|
|
outputs << first_output
|
|
end
|
|
end
|
|
new_arr = output_lines[1..-1]
|
|
new_arr = [] if new_arr.nil?
|
|
if data_str[-1] == "\n"
|
|
new_arr << ""
|
|
end
|
|
outputs += new_arr
|
|
if update_outputs
|
|
if first_output
|
|
update_thread_infos_for_exec(first_output,true) if @flag
|
|
update_infos_for_exec(first_output,true)
|
|
end
|
|
update_thread_infos_for_exec(new_arr,false,true) if @flag
|
|
update_infos_for_exec(new_arr,false,true)
|
|
end
|
|
else
|
|
if @remove_last_line
|
|
outputs = outputs[1..-1]
|
|
outputs = [] if outputs.nil?
|
|
end
|
|
if outputs.count == 0
|
|
outputs.push(data_str)
|
|
else
|
|
outputs[-1] += (data_str rescue "")
|
|
end
|
|
if update_outputs
|
|
update_thread_infos_for_exec(data_str,true) if @flag
|
|
update_infos_for_exec(data_str,true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
channel.on_close do |ch|
|
|
if update
|
|
if @site_construct
|
|
update_thread_infos_for_exec("Finish execing #{command_i18n} on <a href='#{((@site_construct.get_port == "443") ? "https" : "http")}://#{@site_construct.domain_name}#{((@site_construct.get_port=="80" || @site_construct.get_port=="443" || @site_construct.get_port.blank?) ? "" : (':'+@site_construct.get_port))}'>#{@site_construct.domain_name}</a>") if @flag
|
|
update_infos_for_exec("Finish execing #{command_i18n}")
|
|
elsif @thread
|
|
update_thread_infos_for_exec("Finish execing #{command_i18n}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
session.loop
|
|
if output_string
|
|
return outputs.join("\n")
|
|
else
|
|
return outputs
|
|
end
|
|
end
|
|
def exec_ssh_command_by_sudo_and_see_output(session,command,update=1,output_string=false)
|
|
outputs = exec_ssh_command_and_see_output(session,command,update)
|
|
tmp = outputs.join("\n")
|
|
if tmp.include?("Permission denied") || tmp.include?("Operation not permitted")
|
|
outputs = exec_ssh_command_and_see_output(session,"sudo -p 'sudo password:' #{command}",update)
|
|
tmp = outputs.join("\n")
|
|
end
|
|
if output_string
|
|
return tmp
|
|
else
|
|
return outputs
|
|
end
|
|
end
|
|
end |