simonmenke-capricorn 0.2.00

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/app_generators/engine/engine_generator.rb +39 -0
  2. data/app_generators/engine/templates/config/routes.rb +2 -0
  3. data/app_generators/engine/templates/init.rb +1 -0
  4. data/app_generators/engine/templates/lib/engine.rb +1 -0
  5. data/app_generators/engine/templates/rails/init.rb +1 -0
  6. data/bin/capricorn +20 -0
  7. data/lib/capricorn/actor/actions.rb +76 -0
  8. data/lib/capricorn/actor.rb +23 -0
  9. data/lib/capricorn/actors/apache_actor.rb +56 -0
  10. data/lib/capricorn/actors/base_actor.rb +276 -0
  11. data/lib/capricorn/actors/mysql_actor.rb +20 -0
  12. data/lib/capricorn/actors/passenger_actor.rb +19 -0
  13. data/lib/capricorn/actors/plesk_actor.rb +210 -0
  14. data/lib/capricorn/actors/sqlite3_actor.rb +44 -0
  15. data/lib/capricorn/app_runner.rb +119 -0
  16. data/lib/capricorn/apps/dev.rb +24 -0
  17. data/lib/capricorn/apps/engines.rb +33 -0
  18. data/lib/capricorn/apps/jobs.rb +35 -0
  19. data/lib/capricorn/apps/satellite.rb +32 -0
  20. data/lib/capricorn/apps/server.rb +67 -0
  21. data/lib/capricorn/client/auth_token.rb +98 -0
  22. data/lib/capricorn/client.rb +48 -0
  23. data/lib/capricorn/daemon.rb +71 -0
  24. data/lib/capricorn/exception_handler.rb +78 -0
  25. data/lib/capricorn/extentions/rubygems_plugin.rb +27 -0
  26. data/lib/capricorn/extentions/thor_extentions.rb +32 -0
  27. data/lib/capricorn/job_queue.rb +199 -0
  28. data/lib/capricorn/satellite/actions.rb +35 -0
  29. data/lib/capricorn/satellite/dependency_loader.rb +78 -0
  30. data/lib/capricorn/satellite/persistence.rb +50 -0
  31. data/lib/capricorn/satellite.rb +49 -0
  32. data/lib/capricorn/server/daemon.rb +88 -0
  33. data/lib/capricorn/server/proxy.rb +25 -0
  34. data/lib/capricorn/server/security.rb +113 -0
  35. data/lib/capricorn/server.rb +113 -0
  36. data/lib/capricorn/system/config.rb +49 -0
  37. data/lib/capricorn/system/helper.rb +21 -0
  38. data/lib/capricorn/system/options.rb +79 -0
  39. data/lib/capricorn/system/process_user.rb +73 -0
  40. data/lib/capricorn/system/satellites.rb +44 -0
  41. data/lib/capricorn/system/shell.rb +80 -0
  42. data/lib/capricorn/system.rb +159 -0
  43. data/lib/capricorn.rb +100 -0
  44. data/lib/rubygems_plugin.rb +1 -0
  45. data/spec/actor/actions_spec.rb +13 -0
  46. data/spec/spec_helper.rb +1 -0
  47. metadata +99 -0
@@ -0,0 +1,88 @@
1
+
2
+ module Capricorn
3
+ class Server
4
+
5
+ module Daemon # :nodoc:
6
+
7
+ def self.included(base)
8
+ base.extend Capricorn::Server::Daemon::ClassMethods
9
+ end
10
+
11
+ # all a daemon needs to run
12
+ module ClassMethods
13
+
14
+ # construct a Capricorn uri from a DRb uri
15
+ def construct_uri(uri)
16
+ uri = URI.parse(uri)
17
+ uri.scheme = ( Capricorn.system.use_ssl? ? 'ssl+capricorn' : 'capricorn')
18
+ uri.to_s
19
+ end
20
+
21
+ # stop the server
22
+ def stop
23
+ Capricorn.client.stop_server
24
+ end
25
+
26
+ # start the server
27
+ def start
28
+ start_with_failsafe
29
+ end
30
+
31
+ # start the failsafe runner.
32
+ def start_with_failsafe
33
+ $master = true
34
+ stop_server = false
35
+ wait_before_start = 0
36
+ retries = 0
37
+ until stop_server
38
+ if wait_before_start > 0
39
+ sleep(wait_before_start)
40
+ wait_before_start = 0
41
+ end
42
+
43
+ pid = Process.fork { self.run_server }
44
+ return unless pid
45
+ Process.waitpid(pid, 0)
46
+ case $?.exitstatus
47
+ when Capricorn::STOP_STATUS
48
+ stop_server = true
49
+ retries = 0
50
+ when Capricorn::RESTART_STATUS
51
+ wait_before_start = 2
52
+ retries = 0
53
+ when Capricorn::RELOAD_STATUS
54
+ stop_server = true
55
+ retries = 0
56
+ Capricorn::Daemon::PidFile.destroy
57
+ Capricorn.system.run %{#{Capricorn::BIN_PATH} #{ORIGINAL_ARGV.join(' ')}}
58
+ else
59
+ retries += 1
60
+ stop_server = true if retries >= 3
61
+ end
62
+ end
63
+ end
64
+
65
+ # start the actual DRb server
66
+ def run_server
67
+ $master = false
68
+ Dir.chdir(Capricorn.system.root)
69
+
70
+ Capricorn.log "Server started"
71
+ uri = "druby://#{Capricorn.system.server_hostname}:#{Capricorn.system.server_port}"
72
+ DRb.start_service uri, self.proxy, self.options_for_server
73
+ Capricorn.log "listening at #{self.construct_uri(uri)}"
74
+ make_client_cert_public!
75
+
76
+ at_exit do
77
+ Capricorn.system.queue.stop!
78
+ Capricorn.log "Server stopped"
79
+ end
80
+
81
+ DRb.thread.join
82
+ exit($exitstatus || Capricorn::STOP_STATUS)
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module Capricorn
3
+ class Server
4
+ # the proxy object hides all the server internals from the clients.
5
+ class Proxy
6
+
7
+ def initialize(server)
8
+ @server = server
9
+ end
10
+
11
+ def self.allow(*methods)
12
+ methods.each do |method|
13
+ module_eval %{ def #{method}(*args,&block) ; @server.#{method}(*args,&block) ; end }
14
+ end
15
+ end
16
+
17
+ allow :stop_server, :restart_server, :reload_server, :server_version, :update_server, :install_satellite, :uninstall_satellite, :install_engine, :update_engine, :uninstall_engine, :satellites, :queued_jobs, :cancel_job, :immediate_job
18
+
19
+ class << self
20
+ undef_method :allow
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,113 @@
1
+
2
+ module Capricorn
3
+ class Server
4
+ module Security # :nodoc:
5
+
6
+ def self.included(base)
7
+ base.extend Capricorn::Server::Security::ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def options_for_server
13
+
14
+ config = {}
15
+ if Capricorn.system.use_ssl?
16
+ install_quick_cert!
17
+ dump_quick_cert_config!
18
+ run_quick_cert!
19
+
20
+ keypair = Capricorn.system.path('quick_cert', 'capricorn', 'capricorn_keypair.pem')
21
+ cert = Capricorn.system.path('quick_cert', 'capricorn', 'cert_capricorn.pem')
22
+
23
+ config = {
24
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.read(keypair)),
25
+ :SSLCertificate => OpenSSL::X509::Certificate.new(File.read(cert)),
26
+ :SSLVerifyMode => OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
27
+ :SSLCACertificateFile => Capricorn.system.path('quick_cert', 'CA', 'cacert.pem')
28
+ }
29
+ end
30
+
31
+ config
32
+ end
33
+
34
+ def install_quick_cert!
35
+ unless Capricorn.system.find_bin('QuickCert')
36
+ FileUtils.mkdir_p('/tmp/quick_cert')
37
+ File.chmod(0700, '/tmp/quick_cert')
38
+
39
+ Dir.chdir('/tmp/quick_cert') do
40
+ Capricorn.system.run "curl -O #{Capricorn::QUICK_CERT}"
41
+ Capricorn.system.run "tar xzf QuickCert-1.0.2.tar.gz"
42
+
43
+ Dir.chdir('/tmp/quick_cert/QuickCert-1.0.2') do
44
+ Capricorn.system.run "#{Capricorn.system.ruby_path} ./setup.rb config"
45
+ Capricorn.system.run "#{Capricorn.system.ruby_path} ./setup.rb setup"
46
+ Capricorn.system.run "#{Capricorn.system.ruby_path} ./setup.rb install"
47
+ end
48
+ end
49
+
50
+ FileUtils.rm_rf('/tmp/quick_cert')
51
+ end
52
+ end
53
+
54
+ def dump_quick_cert_config!
55
+ return if File.file? Capricorn.system.path('quick_cert', 'qc_config')
56
+
57
+ config = %{
58
+ full_hostname = `hostname`.strip
59
+ domainname = full_hostname.split('.')[1..-1].join('.')
60
+ hostname = full_hostname.split('.')[0]
61
+
62
+ CA[:hostname] = hostname
63
+ CA[:domainname] = domainname
64
+ CA[:CA_dir] = File.join Dir.pwd, "CA"
65
+ CA[:password] = '#{rand(100_000)}'
66
+
67
+ CERTS << {
68
+ :type => 'server',
69
+ :hostname => 'capricorn',
70
+ # :password => '#{rand(100_000)}',
71
+ }
72
+
73
+ CERTS << {
74
+ :type => 'client',
75
+ :user => 'core',
76
+ :email => 'core@mrhenry.be',
77
+ }
78
+ }
79
+ FileUtils.mkdir_p(Capricorn.system.path('quick_cert'))
80
+ File.chmod(0700, Capricorn.system.path('quick_cert'))
81
+ File.open(Capricorn.system.path('quick_cert', 'qc_config'), 'w+') { |f| f.write config }
82
+ end
83
+
84
+ def run_quick_cert!
85
+ return if File.directory? Capricorn.system.path('quick_cert', 'CA')
86
+
87
+ Dir.chdir(Capricorn.system.path('quick_cert')) do
88
+ Capricorn.system.run Capricorn.system.find_bin('QuickCert')
89
+ end
90
+ end
91
+
92
+ def make_client_cert_public!
93
+ token = nil
94
+ if Capricorn.system.use_ssl?
95
+ token = Capricorn::Client::AuthToken.new(
96
+ :target_uri => self.construct_uri(DRb.uri),
97
+ :verify_mode => OpenSSL::SSL::VERIFY_PEER,
98
+ :private_key_data => File.read(Capricorn.system.path('quick_cert', 'core', 'core_keypair.pem')),
99
+ :certificate_data => File.read(Capricorn.system.path('quick_cert', 'core', 'cert_core.pem')),
100
+ :ca_certificate_data => File.read(Capricorn.system.path('quick_cert', 'CA', 'cacert.pem'))
101
+ )
102
+ else
103
+ token = Capricorn::Client::AuthToken.new(
104
+ :target_uri => self.construct_uri(DRb.uri))
105
+ end
106
+ token.dump_file(Capricorn.system.path('core.token'))
107
+ FileUtils.chmod_R(0775, Capricorn.system.path('core.token'))
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,113 @@
1
+
2
+ module Capricorn
3
+
4
+ # the Server is supposed to be a simple DRb interface to the System.
5
+ class Server < Capricorn::Daemon::Base
6
+ include DRbUndumped
7
+
8
+ autoload :Proxy, File.dirname(__FILE__)+'/server/proxy'
9
+ autoload :Daemon, File.dirname(__FILE__)+'/server/daemon'
10
+ autoload :Security, File.dirname(__FILE__)+'/server/security'
11
+
12
+ include Capricorn::Server::Daemon
13
+ include Capricorn::Server::Security
14
+
15
+ def self.shared
16
+ @shared ||= new
17
+ end
18
+
19
+ def self.proxy
20
+ @proxy ||= Capricorn::Server::Proxy.new(self.shared)
21
+ end
22
+
23
+ def stop_server
24
+ Capricorn.log "stopping the server..."
25
+ $exitstatus = Capricorn::STOP_STATUS
26
+ DRb.stop_service
27
+ end
28
+
29
+ def restart_server
30
+ Capricorn.log "restarting the server..."
31
+ $exitstatus = Capricorn::RESTART_STATUS
32
+ DRb.stop_service
33
+ end
34
+
35
+ def reload_server
36
+ Capricorn.log "reloading the server..."
37
+ $exitstatus = Capricorn::RELOAD_STATUS
38
+ DRb.stop_service
39
+ end
40
+
41
+ def server_version
42
+ Capricorn.version
43
+ end
44
+
45
+ def update_server
46
+ if Capricorn.system.gem_update('capricorn', :user => 'root')
47
+ reload_server
48
+ end
49
+ end
50
+
51
+ def install_satellite(domain)
52
+ Capricorn.system.install_satellite(domain)
53
+ end
54
+
55
+ def uninstall_satellite(domain)
56
+ satellite = Capricorn.system.find_satellite(domain)
57
+ if satellite
58
+ Capricorn.system.uninstall_satellite(satellite)
59
+ else
60
+ Capricorn.log "Satellite not found (#{domain})"
61
+ end
62
+ end
63
+
64
+ def install_engine(domain, name, options={})
65
+ satellite = Capricorn.system.find_satellite(domain)
66
+ if satellite
67
+ Capricorn.system.install_engine(satellite, name, options)
68
+ else
69
+ Capricorn.log "Satellite not found (#{domain})"
70
+ end
71
+ end
72
+
73
+ def update_engine(domain, name, options={})
74
+ satellite = Capricorn.system.find_satellite(domain)
75
+ if satellite
76
+ Capricorn.system.update_engine(satellite, name, options)
77
+ else
78
+ Capricorn.log "Satellite not found (#{domain})"
79
+ end
80
+ end
81
+
82
+ def uninstall_engine(domain, name)
83
+ satellite = Capricorn.system.find_satellite(domain)
84
+ if satellite
85
+ Capricorn.system.uninstall_engine(satellite, name)
86
+ else
87
+ Capricorn.log "Satellite not found (#{domain})"
88
+ end
89
+ end
90
+
91
+ def satellites
92
+ Capricorn.system.satellites
93
+ end
94
+
95
+ def queued_jobs
96
+ queued_jobs = []
97
+ Capricorn.system.queue.each do |job, canceled, immediated|
98
+ job_delay = job.delay
99
+ queued_jobs.push([job.id, job.name, canceled, immediated, job.running?, job.waiting?, job_delay])
100
+ end
101
+ queued_jobs
102
+ end
103
+
104
+ def cancel_job(id)
105
+ Capricorn.system.queue.cancel(id)
106
+ end
107
+
108
+ def immediate_job(id)
109
+ Capricorn.system.queue.immediate(id)
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module Config
5
+
6
+ def use_development!
7
+ environment { 'development' }
8
+ end
9
+
10
+ def development?
11
+ environment == 'development'
12
+ end
13
+
14
+ def use_production!
15
+ environment { 'production' }
16
+ end
17
+
18
+ def production?
19
+ environment == 'production'
20
+ end
21
+
22
+ def environment(&block)
23
+ option(:environment, block) { |s,v| v or 'production' }
24
+ end
25
+
26
+ def use_ssl!
27
+ option(:use_ssl, lambda { true })
28
+ end
29
+
30
+ def use_ssl?
31
+ option(:use_ssl, nil)
32
+ end
33
+
34
+ def bind(hostname=nil, port=nil)
35
+ server_hostname { hostname }
36
+ server_port { port }
37
+ end
38
+
39
+ def server_hostname(&block)
40
+ option(:server_hostname, block) { |v| v or 'localhost' }
41
+ end
42
+
43
+ def server_port(&block)
44
+ option(:server_port, block) { |v| v or 5000 }
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module Helper
5
+
6
+ def use(actor)
7
+ actor_klass = (Capricorn::Actors.const_get(actor) rescue nil)
8
+ raise "Actor not found! (#{actor})" unless actor_klass
9
+
10
+ actor_helper = (actor_klass.const_get('Helper') rescue nil)
11
+ extend actor_helper if actor_helper
12
+
13
+ actor_config = (actor_klass.const_get('Config') rescue nil)
14
+ extend actor_config if actor_config
15
+
16
+ @actors.push(actor_klass)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module Options
5
+
6
+ def option(name, proc, &handler)
7
+ if proc
8
+ @option_descriptors.push({
9
+ :name => name.to_sym,
10
+ :proc => proc,
11
+ :handler => handler
12
+ })
13
+ else
14
+ if handler && !@options.key?(name.to_sym)
15
+ @options[name.to_sym] = handler.call(nil)
16
+ end
17
+ @options[name.to_sym]
18
+ end
19
+ end
20
+
21
+ def satellite_option(name, proc, &handler)
22
+ if proc
23
+ @satellite_option_descriptors.push({
24
+ :name => name.to_sym,
25
+ :proc => proc,
26
+ :handler => handler
27
+ })
28
+ else
29
+ if handler && !@satellite_options.key?(name.to_sym)
30
+ @satellite_options[name.to_sym] = handler.call(@current_satellite, nil)
31
+ end
32
+ @satellite_options[name.to_sym]
33
+ end
34
+ end
35
+
36
+ def set_satellite_option(name, value)
37
+ @satellite_options[name.to_sym] = value
38
+ end
39
+
40
+ def resolve_options_with(satellite)
41
+ @current_satellite = satellite
42
+ resolve_options!
43
+
44
+ result = yield
45
+
46
+ @current_satellite = nil
47
+ resolve_options!
48
+
49
+ result
50
+ end
51
+
52
+ def resolve_options!
53
+ resolve_options_for_system!
54
+ resolve_options_for_satellite!
55
+ end
56
+
57
+ def resolve_options_for_system!
58
+ @options = {}
59
+ @option_descriptors.each do |option|
60
+ value = option[:proc].call if option[:proc]
61
+ value = option[:handler].call(value) if option[:handler]
62
+ @options[option[:name]] = value
63
+ end
64
+ end
65
+
66
+ def resolve_options_for_satellite!
67
+ @satellite_options = {}
68
+ if @current_satellite
69
+ @satellite_option_descriptors.each do |option|
70
+ value = option[:proc].call(@current_satellite) if option[:proc]
71
+ value = option[:handler].call(@current_satellite, value) if option[:handler]
72
+ @satellite_options[option[:name]] = value
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module ProcessUser
5
+
6
+ # get the uid for a user name
7
+ def get_uid(username)
8
+ return username if Numeric === username
9
+ Etc.getpwnam(username).uid
10
+ end
11
+
12
+ # get the gid for a group name
13
+ def get_gid(groupname)
14
+ return groupname if Numeric === groupname
15
+ Etc.getgrnam(groupname).gid
16
+ end
17
+
18
+ # get the user name for a uid (or the current process user)
19
+ def get_user_name(uid=Process.uid)
20
+ return uid if String === uid
21
+ Etc.getpwuid(uid).name
22
+ end
23
+
24
+ # get the group name for a gid (or the current process group)
25
+ def get_group_name(gid=Process.gid)
26
+ return gid if String === gid
27
+ Etc.getgrgid(gid).name
28
+ end
29
+
30
+ # is this process running as this user?
31
+ def is_user(username)
32
+ uid = get_uid(username)
33
+ Process.euid == uid and Process.uid == uid
34
+ end
35
+
36
+ # is this process running as this group?
37
+ def is_group(groupname)
38
+ gid = get_gid(groupname)
39
+ Process.egid == gid and Process.gid == gid
40
+ end
41
+
42
+ # switch this user to the specified user and group
43
+ def switch_to_user(username, groupname=nil)
44
+ different_uid = (Process.euid != get_uid(username))
45
+ different_gid = (Process.egid != get_gid(groupname)) if groupname
46
+
47
+ if groupname and different_gid
48
+ Process.gid = Process.egid = get_gid(groupname)
49
+ end
50
+
51
+ if different_uid
52
+ Process.uid = Process.euid = get_uid(username)
53
+ end
54
+ end
55
+
56
+ # run the passed block as the specified user and group
57
+ def as_user(username, groupname=nil, &block)
58
+ euid = Process.euid
59
+ egid = Process.egid
60
+
61
+ value = nil
62
+ begin
63
+ switch_to_user(username, groupname)
64
+ value = block.call
65
+ ensure
66
+ switch_to_user(euid, egid)
67
+ end
68
+ value
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module Satellites
5
+
6
+ def satellites_hash
7
+ @satellites || load_satellites
8
+ end
9
+
10
+ def satellites
11
+ satellites_hash.values
12
+ end
13
+
14
+ def find_satellite(domain)
15
+ satellites_hash[domain]
16
+ end
17
+
18
+ def save_satellite!(satellite)
19
+ satellites_hash[satellite.domain] = satellite
20
+ satellite.dump_file(self.path('satellites', "#{satellite.domain}.yml"))
21
+ end
22
+
23
+ def destroy_satellite!(satellite)
24
+ satellites_hash.delete(satellite.domain)
25
+ FileUtils.rm_f(self.path('satellites', "#{satellite.domain}.yml"))
26
+ end
27
+
28
+ private
29
+
30
+ def load_satellites
31
+ @satellites = {}
32
+
33
+ FileUtils.mkdir_p(self.path('satellites'))
34
+ Dir.glob(self.path('satellites', '*.yml')).each do |yml|
35
+ satellite = Capricorn::Satellite.load_file(yml)
36
+ @satellites[satellite.domain] = satellite
37
+ end
38
+
39
+ @satellites
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,80 @@
1
+
2
+ module Capricorn
3
+ class System
4
+ module Shell
5
+
6
+ def run(cmd)
7
+ as_user 'root', 'wheel' do
8
+ Capricorn.log "[#{get_user_name}]> "+cmd
9
+ cmd = "bash -l -c #{cmd.inspect}"
10
+ output = %x[#{cmd} 2>&1]
11
+ Capricorn.log output if development?
12
+ output
13
+ end
14
+ end
15
+
16
+ def user_run(username, cmd)
17
+ as_user 'root', 'wheel' do
18
+ Capricorn.log "[#{username}]> "+cmd
19
+ cmd = "su #{username} -l -c #{cmd.inspect}"
20
+ output = %x[#{cmd} 2>&1]
21
+ Capricorn.log output if development?
22
+ output
23
+ end
24
+ end
25
+
26
+ def popen(cmd, *args, &block)
27
+ as_user 'root', 'wheel' do
28
+ Capricorn.log cmd
29
+ cmd = "bash -l -c #{cmd.inspect}"
30
+ IO.popen(cmd, *args, &block)
31
+ end
32
+ end
33
+
34
+ def user_popen(username, cmd, *args, &block)
35
+ as_user 'root', 'wheel' do
36
+ Capricorn.log cmd
37
+ cmd = "su #{username} -l -c #{cmd.inspect}"
38
+ IO.popen(cmd, *args, &block)
39
+ end
40
+ end
41
+
42
+ def find_bin(*names)
43
+ names = names.flatten.compact.uniq
44
+ unless names.empty?
45
+ names = names.join(' ')
46
+ popen("which #{names}", 'r') do |f|
47
+
48
+ until f.eof?
49
+ path = f.readline
50
+ next unless path
51
+ path = path.strip
52
+ next if path =~ /(Last login)|(You have new)|(Using ruby)/
53
+ return path
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+
60
+ def user_find_bin(username, *names)
61
+ names = names.flatten.compact.uniq
62
+ unless names.empty?
63
+ names = names.join(' ')
64
+ user_popen(username, "which #{names}", 'r') do |f|
65
+
66
+ until f.eof?
67
+ path = f.readline
68
+ next unless path
69
+ path = path.strip
70
+ next if path =~ /(Last login)|(You have new)|(Using ruby)/
71
+ return path
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end