sesh 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +675 -0
- data/README.md +17 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/sesh +3 -0
- data/lib/sesh/cli.rb +244 -0
- data/lib/sesh/inferences.rb +36 -0
- data/lib/sesh/logger.rb +30 -0
- data/lib/sesh/ssh_control.rb +46 -0
- data/lib/sesh/tmux_control.rb +51 -0
- data/lib/sesh/version.rb +1 -1
- data/lib/sesh.rb +46 -0
- data/sesh.gemspec +32 -0
- metadata +103 -5
- data/bin/sesh +0 -254
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Sesh: remote background sessions powered by tmux and tmuxinator.
|
2
|
+
Runs a headless tmuxinator session for remote slave machines to connect to.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
sesh command [project]
|
7
|
+
|
8
|
+
Leave project blank to use the name of your current directory.
|
9
|
+
|
10
|
+
## Commands
|
11
|
+
|
12
|
+
sesh new Create a new tmuxinator configuration.
|
13
|
+
sesh start [project] Start a Sesh session for a project.
|
14
|
+
sesh stop [project] Stop a Sesh session for a project.
|
15
|
+
sesh list List running Sesh sessions on this machine.
|
16
|
+
sesh enslave [project] [user@host] Connect a slave to a local Sesh session.
|
17
|
+
sesh connect [project] user@host Connect as a slave to a Sesh session.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "sesh"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/sesh
ADDED
data/lib/sesh/cli.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'sesh'
|
2
|
+
require 'tmuxinator'
|
3
|
+
require 'optparse'
|
4
|
+
require 'yaml'
|
5
|
+
require 'colorize'
|
6
|
+
require 'open3'
|
7
|
+
require 'deep_merge'
|
8
|
+
|
9
|
+
module Sesh
|
10
|
+
class Cli
|
11
|
+
def self.start
|
12
|
+
puts "Sesh v#{Sesh::VERSION}".green
|
13
|
+
if ARGV.empty? or ARGV.include? '-h' or ARGV.include? '--help'
|
14
|
+
puts HELP_BANNER.blue; exit end
|
15
|
+
|
16
|
+
parse_options!
|
17
|
+
@tmux_control = TmuxControl.new @options[:project], @options[:tmux]
|
18
|
+
@ssh_control = SshControl.new @tmux_control, @options[:ssh]
|
19
|
+
|
20
|
+
if @command
|
21
|
+
case @command
|
22
|
+
when 'start'
|
23
|
+
if @tmux_control.already_running?
|
24
|
+
Logger.fatal "Sesh project '#{@options[:project]}' is already running!"
|
25
|
+
else
|
26
|
+
Logger.debug "Starting Sesh project '#{@options[:project]}'..."
|
27
|
+
end
|
28
|
+
@tmux_control.kill_running_processes
|
29
|
+
if @tmux_control.issue_start_command! &&
|
30
|
+
Logger.show_progress_until(-> { @tmux_control.already_running? })
|
31
|
+
sleep 1
|
32
|
+
if @tmux_control.already_running?
|
33
|
+
@tmux_control.store_pids_from_session!
|
34
|
+
Logger.debug 'Sesh started successfully.'
|
35
|
+
puts
|
36
|
+
else Logger.fatal 'Sesh failed to start!' end
|
37
|
+
else Logger.fatal 'Sesh failed to start after ten seconds!' end
|
38
|
+
when 'stop'
|
39
|
+
if @tmux_control.already_running?
|
40
|
+
Logger.debug "Stopping Sesh project '#{@options[:project]}'..."
|
41
|
+
else
|
42
|
+
Logger.fatal "Sesh project '#{@options[:project]}' is not running!"
|
43
|
+
end
|
44
|
+
@tmux_control.kill_running_processes
|
45
|
+
@tmux_control.issue_stop_command!
|
46
|
+
if $? && Logger.show_progress_until(-> { !@tmux_control.already_running? })
|
47
|
+
Logger.debug 'Sesh stopped successfully.'
|
48
|
+
puts
|
49
|
+
else
|
50
|
+
Logger.fatal 'Sesh failed to stop after ten seconds!'
|
51
|
+
end
|
52
|
+
when 'restart'
|
53
|
+
Logger.fatal("Sesh project '#{@options[:project]}' is not running!") unless already_running?
|
54
|
+
puts `sesh stop #{@options[:project]} #{@options if @options.any?}`.strip
|
55
|
+
sleep 0.5
|
56
|
+
puts `sesh start #{@options[:project]} #{@options if @options.any?}`.strip
|
57
|
+
when 'new'
|
58
|
+
config = Tmuxinator::Config.project(@options[:project])
|
59
|
+
unless Tmuxinator::Config.exists?(@options[:project])
|
60
|
+
template = File.join(File.dirname(File.expand_path(__FILE__)),
|
61
|
+
"../lib/sesh/assets/sample.yml")
|
62
|
+
erb = Erubis::Eruby.new(File.read(template)).result(binding)
|
63
|
+
File.open(config, "w") { |f| f.write(erb) }
|
64
|
+
end
|
65
|
+
|
66
|
+
Kernel.system("#{Inferences.infer_default_editor} #{config}") ||
|
67
|
+
Tmuxinator::Cli.new.doctor
|
68
|
+
puts
|
69
|
+
when 'edit'
|
70
|
+
config = Tmuxinator::Config.project(@options[:project])
|
71
|
+
if Tmuxinator::Config.exists? @options[:project]
|
72
|
+
Kernel.system("#{Inferences.infer_default_editor} #{config}") ||
|
73
|
+
Tmuxinator::Cli.new.doctor
|
74
|
+
puts
|
75
|
+
else
|
76
|
+
Logger.fatal "Sesh project '#{@options[:project]}' does not exist yet!"
|
77
|
+
end
|
78
|
+
when 'list'
|
79
|
+
output = Sesh.format_and_run_command <<-BASH
|
80
|
+
ps aux | grep tmux | grep "env TMUX='' mux start" \
|
81
|
+
| grep -v "[g]rep" | sed -e "s/.*mux start \\(.*\\)/\\1/"
|
82
|
+
BASH
|
83
|
+
running_projects = output.split("\n")
|
84
|
+
pcount = running_projects.count
|
85
|
+
if pcount > 0
|
86
|
+
Logger.info "#{pcount} project#{pcount>1 ? 's':''} currently running:"
|
87
|
+
puts running_projects
|
88
|
+
puts
|
89
|
+
else
|
90
|
+
Logger.fatal "There are no Sesh projects currently running."
|
91
|
+
end
|
92
|
+
when 'connect'
|
93
|
+
if @options[:ssh][:local_addr] == @options[:ssh][:remote_addr]
|
94
|
+
unless @tmux_control.already_running?
|
95
|
+
Logger.fatal "Sesh project '#{@options[:project]}' is not running!"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
system @ssh_control.enter_slave_mode_command(@options[:ssh][:local_addr])
|
99
|
+
when 'enslave'
|
100
|
+
Logger.fatal("Sesh project '#{@options[:project]}' is not running!") unless @tmux_control.already_running?
|
101
|
+
Logger.fatal("You must specify a machine to enslave! Eg: user@ip") if @options[:ssh][:remote_addr].nil?
|
102
|
+
Logger.debug "Attempting to connect #{@options[:ssh][:remote_addr]} to Sesh project '#{@options[:project]}'..."
|
103
|
+
if @ssh_control.enslave_peer!
|
104
|
+
Logger.debug "Sesh client connected successfully."
|
105
|
+
puts
|
106
|
+
else
|
107
|
+
Logger.fatal 'Sesh client failed to start.'
|
108
|
+
end
|
109
|
+
when 'run'
|
110
|
+
unless ARGV.any?
|
111
|
+
Logger.fatal 'A second command is required!'
|
112
|
+
end
|
113
|
+
@shell_command = ARGV.join(' ')
|
114
|
+
puts "Subcommand: #{@shell_command}"
|
115
|
+
when 'rspec'
|
116
|
+
unless ARGV.any?
|
117
|
+
Logger.fatal 'A path to a spec is required!'
|
118
|
+
end
|
119
|
+
@shell_command = ARGV.join(' ')
|
120
|
+
puts "Spec: #{@shell_command}"
|
121
|
+
else
|
122
|
+
Logger.fatal "Command not recognized!"
|
123
|
+
end
|
124
|
+
exit 0
|
125
|
+
end
|
126
|
+
|
127
|
+
Logger.fatal 'You must specify a command.'
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.parse_options!
|
131
|
+
@command = ARGV.shift
|
132
|
+
|
133
|
+
# Load config from a YAML file in the project root if available.
|
134
|
+
@config_filepath = nil
|
135
|
+
@config_friendly_filepath = nil
|
136
|
+
POSSIBLE_CONFIG_LOCATIONS.each do |path|
|
137
|
+
fullpath = File.join Dir.pwd, path
|
138
|
+
next unless File.exists? fullpath
|
139
|
+
@config_filepath = fullpath
|
140
|
+
@config_friendly_filepath = path
|
141
|
+
break
|
142
|
+
end
|
143
|
+
@defaults = DEFAULT_OPTIONS.dup
|
144
|
+
if @config_filepath.nil?
|
145
|
+
@config_friendly_filepath = POSSIBLE_CONFIG_LOCATIONS[0]
|
146
|
+
else
|
147
|
+
loaded_config = deep_symbolize YAML::load_file(@config_filepath)
|
148
|
+
@defaults.deep_merge! loaded_config
|
149
|
+
end
|
150
|
+
|
151
|
+
# Parse options given to the command.
|
152
|
+
parsed_options = @defaults.dup
|
153
|
+
OptionParser.new do |opts|
|
154
|
+
opts.banner = HELP_BANNER
|
155
|
+
|
156
|
+
opts.on("-p", "--project=project", 'Project') {|v|
|
157
|
+
parsed_options[:project] = v }
|
158
|
+
|
159
|
+
opts.on("-L", "--local-ssh-addr=addr", 'Local SSH Address') {|v|
|
160
|
+
parsed_options[:ssh][:local_addr] = v }
|
161
|
+
opts.on("-R", "--remote-ssh-addr=addr", 'Remote SSH Address') {|v|
|
162
|
+
parsed_options[:ssh][:remote_addr] = v }
|
163
|
+
|
164
|
+
opts.on('-S', '--tmux-socket-file=path', 'Path to Tmux Socket File') {|v|
|
165
|
+
# fatal("Socket file #{v} does not exist.") unless File.exist?(v)
|
166
|
+
parsed_options[:tmux][:socket_file] = v }
|
167
|
+
opts.on('-P', '--tmux-pids-file=path', 'Path to Tmux Pids File') {|v|
|
168
|
+
parsed_options[:tmux][:pids_file] = v }
|
169
|
+
|
170
|
+
# # target_opts = DEPLOYMENT_TARGETS.join '|'
|
171
|
+
# opts.on("-T", "--target=target", 'Titanium Deployment Target') do |v|
|
172
|
+
# if DEPLOYMENT_TARGETS.include? v
|
173
|
+
# platform = parsed_options[:titanium_command][:platform]
|
174
|
+
# if v == 'xcode'
|
175
|
+
# parsed_options[:titanium_command][:run_with_xcode] = true
|
176
|
+
# v = 'simulator'
|
177
|
+
# end
|
178
|
+
# if v == 'emulator' && platform == 'ios' then v = 'simulator'
|
179
|
+
# elsif v == 'simulator' && platform == 'android' then v = 'emulator' end
|
180
|
+
# parsed_options[:titanium_command][:target] = v
|
181
|
+
# else fatal "Target \"#{v}\" not recognized." end
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# opts.on('-O', '--output-dir=dir', 'Titanium Output Directory') {|v|
|
185
|
+
# parsed_options[:titanium_command][:output_dir] = v }
|
186
|
+
#
|
187
|
+
# opts.on('-L', '--log-level=level', 'Titanium Log Level') {|v|
|
188
|
+
# parsed_options[:titanium_command][:log_level] = v }
|
189
|
+
#
|
190
|
+
# opts.on('-V', '--developer-certificate=certificate',
|
191
|
+
# 'iOS Developer Certificate') {|v|
|
192
|
+
# parsed_options[:ios_credentials][:development][:developer_certificate] = v
|
193
|
+
# parsed_options[:ios_credentials][:production][:developer_certificate] = v }
|
194
|
+
# opts.on('-P', '--provisioning-profile=profile',
|
195
|
+
# 'iOS Provisioning Profile') {|v|
|
196
|
+
# parsed_options[:ios_credentials][:development][:provisioning_profile] = v
|
197
|
+
# parsed_options[:ios_credentials][:production][:provisioning_profile] = v }
|
198
|
+
#
|
199
|
+
# opts.on('-U', '--simulator-udid=udid', 'iOS Simulator UDID') {|v|
|
200
|
+
# parsed_options[:ios_simulator][:udid] = v }
|
201
|
+
# opts.on('-D', '--devicetypeid=dtid', 'iOS Simulator DeviceTypeID') {|v|
|
202
|
+
# parsed_options[:ios_simulator][:devicetypeid] = v }
|
203
|
+
#
|
204
|
+
# opts.on('--no-autofocus', 'Disable iOS Simulator Autofocus') {
|
205
|
+
# parsed_options[:ios_simulator][:autofocus] = false }
|
206
|
+
|
207
|
+
opts.on('--help', 'Prints this help') { puts opts.banner; exit }
|
208
|
+
end.parse!
|
209
|
+
@options = parsed_options
|
210
|
+
if @options[:project].nil?
|
211
|
+
project_required = !%w(new list).include?(@command)
|
212
|
+
ARGV.each {|a|
|
213
|
+
if Tmuxinator::Config.exists? a
|
214
|
+
@options[:project] = ARGV.delete a
|
215
|
+
break end } if project_required && ARGV.any?
|
216
|
+
@options[:project] ||= Inferences.infer_project_from_current_directory
|
217
|
+
if project_required && @options[:project].nil?
|
218
|
+
Logger.warn 'A matching Sesh project could not be found.'
|
219
|
+
Logger.fatal 'Hint: run sesh new or specify an existing project with your commmand.'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
@options[:tmux][:socket_file] ||= "/tmp/#{@options[:project]}.sock"
|
223
|
+
@options[:tmux][:pids_file] ||= "/tmp/#{@options[:project]}.pids.txt"
|
224
|
+
@options[:ssh][:local_addr] ||= Sesh::Inferences.infer_local_ssh_addr
|
225
|
+
if @options[:ssh][:remote_addr].nil?
|
226
|
+
if ARGV.any?
|
227
|
+
ARGV.each {|a|
|
228
|
+
if a =~ /\.local$/ || a =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ || a =~ /^(.+)@(.+)$/
|
229
|
+
@options[:ssh][:remote_addr] = a
|
230
|
+
break end }
|
231
|
+
@options[:ssh][:remote_addr] ||= ARGV.shift
|
232
|
+
end
|
233
|
+
if @options[:ssh][:remote_addr].nil?
|
234
|
+
if %w(enslave run rspec).include? @command
|
235
|
+
Logger.warn 'A remote address is required.'
|
236
|
+
Logger.fatal 'Hint: specify a remote ssh address using the -R flag.'
|
237
|
+
elsif %w(connect).include? @command
|
238
|
+
@options[:ssh][:remote_addr] = Inferences.infer_local_ssh_addr
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'sesh'
|
2
|
+
require 'tmuxinator'
|
3
|
+
|
4
|
+
module Sesh
|
5
|
+
module Inferences
|
6
|
+
def self.infer_project_from_current_directory
|
7
|
+
output = `printf '%q\n' "${PWD##*/}"`.strip
|
8
|
+
return output if Tmuxinator::Config.exists?(output)
|
9
|
+
end
|
10
|
+
def self.infer_local_ssh_addr
|
11
|
+
inferred_user = `echo $USER`.strip
|
12
|
+
inferred_hostname = `scutil --get LocalHostName`.strip.downcase
|
13
|
+
inferred_hostname += '.local' unless inferred_hostname =~ /\.local$/
|
14
|
+
"#{inferred_user}@#{inferred_hostname}"
|
15
|
+
end
|
16
|
+
def self.infer_terminal_app
|
17
|
+
if OS.mac?
|
18
|
+
output = `osascript -e 'try' -e 'get exists application "iTerm"' -e 'end try'`.strip
|
19
|
+
output.length > 0 ? 'iTerm' : fatal("iTerm 2 is not installed.") # 'Terminal'
|
20
|
+
# TODO: support more platforms
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def self.infer_default_editor
|
24
|
+
if OS.windows? then 'notepad.exe'
|
25
|
+
else o = `echo $EDITOR`.strip; o = 'vim' unless o.length > 0; o end
|
26
|
+
end
|
27
|
+
|
28
|
+
module OS
|
29
|
+
def OS.windows?
|
30
|
+
(/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end
|
31
|
+
def OS.mac?; (/darwin/ =~ RUBY_PLATFORM) != nil end
|
32
|
+
def OS.unix?; !OS.windows? end
|
33
|
+
def OS.linux?; OS.unix? and not OS.mac? end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/sesh/logger.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sesh'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
module Sesh
|
5
|
+
class Logger
|
6
|
+
def self.debug(msg) $stderr.puts "> #{msg}" end
|
7
|
+
def self.fatal(msg)
|
8
|
+
$stderr.puts msg.red; $stderr.puts; exit 1 end
|
9
|
+
def self.warn(msg, nest_level=0)
|
10
|
+
$stderr.puts "#{' ' * nest_level * 2}> #{msg}".yellow end
|
11
|
+
def self.info(msg, nest_level=0)
|
12
|
+
$stdout.puts "#{' ' * nest_level * 2}> #{msg}".blue end
|
13
|
+
def self.success(msg, nest_level=0)
|
14
|
+
$stdout.puts "#{' ' * nest_level * 2}> #{msg}".green end
|
15
|
+
|
16
|
+
def self.show_progress_until(condition_lambda, timeout=10)
|
17
|
+
started_progress_at = Time.now
|
18
|
+
return true if condition_lambda[]
|
19
|
+
print '> '
|
20
|
+
until condition_lambda[] or Time.now - started_progress_at > timeout
|
21
|
+
print '.'
|
22
|
+
$stdout.flush
|
23
|
+
sleep 0.5
|
24
|
+
end
|
25
|
+
puts
|
26
|
+
return condition_lambda[]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sesh'
|
2
|
+
|
3
|
+
module Sesh
|
4
|
+
class SshControl
|
5
|
+
def initialize(tmux_control, options={})
|
6
|
+
@tmux_control = tmux_control
|
7
|
+
@project = @tmux_control.project
|
8
|
+
@options = DEFAULT_OPTIONS[:ssh].merge(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def connection_command(addr=nil)
|
12
|
+
addr ||= @options[:local_addr]
|
13
|
+
tmux_cmd = @tmux_control.connection_command
|
14
|
+
return tmux_cmd if addr == @options[:local_addr]
|
15
|
+
"ssh #{addr} -t '#{tmux_cmd}'"
|
16
|
+
end
|
17
|
+
|
18
|
+
def enter_slave_mode_command(addr=nil)
|
19
|
+
@term_app ||= Inferences.infer_terminal_app
|
20
|
+
case @term_app
|
21
|
+
when 'iTerm'
|
22
|
+
tell_term_app = 'tell application "' + @term_app + '"'
|
23
|
+
tell_term_process = 'tell application "System Events" to tell process "' +
|
24
|
+
@term_app + '"'
|
25
|
+
Sesh.format_command <<-BASH
|
26
|
+
osascript \
|
27
|
+
-e '#{tell_term_app} to activate' \
|
28
|
+
-e '#{tell_term_process} to keystroke \"n\" using command down' \
|
29
|
+
-e 'delay 1' \
|
30
|
+
-e "#{tell_term_app.gsub('"', '\\"')} to tell session -1 of current \
|
31
|
+
terminal to write text \\"#{connection_command(addr)}\\"" \
|
32
|
+
-e '#{tell_term_process} to keystroke return using command down'
|
33
|
+
BASH
|
34
|
+
when 'Terminal' then Sesh.format_command connection_command(addr)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def enslave_peer!
|
39
|
+
output = Sesh.format_and_run_command <<-BASH
|
40
|
+
ssh #{@options[:remote_addr]} "sesh connect #{@project} #{@options[:local_addr]}"
|
41
|
+
BASH
|
42
|
+
puts output
|
43
|
+
$?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'sesh'
|
2
|
+
|
3
|
+
module Sesh
|
4
|
+
class TmuxControl
|
5
|
+
def initialize(project_name, options={})
|
6
|
+
@project = project || Inferences::infer_project_from_current_directory
|
7
|
+
@options = DEFAULT_OPTIONS[:tmux].merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def already_running?
|
11
|
+
`ps aux | grep "#{project_name_matcher}"`.strip.length > 0 end
|
12
|
+
|
13
|
+
def project_name_matcher
|
14
|
+
pn = @project.gsub '-', '\-'
|
15
|
+
"[t]mux.*[#{pn[0]}]#{pn[1..-1]}" end
|
16
|
+
|
17
|
+
def issue_start_command!
|
18
|
+
cmd = Sesh.format_command <<-BASH
|
19
|
+
tmux -S "#{@options[:socket_file]}" new-session -d "eval \$SHELL -l; env TMUX='' mux start #{@project}" 2>&1
|
20
|
+
BASH
|
21
|
+
output = `#{cmd}`.strip
|
22
|
+
return true if output.length == 0
|
23
|
+
Logger.warn "Tmux failed to start with the following error: #{output}"; false
|
24
|
+
end
|
25
|
+
|
26
|
+
def issue_stop_command!; `pkill -f "#{project_name_matcher}"` end
|
27
|
+
|
28
|
+
def connection_command; "tmux -S #{@options[:socket_file]} a" end
|
29
|
+
|
30
|
+
def obtain_pids_from_session
|
31
|
+
# TODO: grep this to just those pids from the current project
|
32
|
+
`tmux list-panes -s -F "\#{pane_pid} \#{pane_current_command}" | grep -v tmux | awk '{print $1}'`.strip.lines end
|
33
|
+
def store_pids_from_session!
|
34
|
+
File.open(@options[:pids_file], 'w') {|f|
|
35
|
+
obtain_pids_from_session.each{|pid| f.puts pid } }
|
36
|
+
end
|
37
|
+
|
38
|
+
def kill_running_processes
|
39
|
+
if File.exists? @options[:pids_file]
|
40
|
+
File.readlines(@options[:pids_file]).each{|pid| kill_process! pid }
|
41
|
+
File.delete @options[:pids_file]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def kill_process!(pid); `kill -9 #{pid}` end
|
45
|
+
|
46
|
+
# Getter methods for passthru to SshControl class
|
47
|
+
def project; @project end
|
48
|
+
def options; @options end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/sesh/version.rb
CHANGED
data/lib/sesh.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sesh/version'
|
2
|
+
require 'sesh/logger'
|
3
|
+
require 'sesh/inferences'
|
4
|
+
require 'sesh/ssh_control'
|
5
|
+
require 'sesh/tmux_control'
|
6
|
+
require 'sesh/cli'
|
7
|
+
|
8
|
+
module Sesh
|
9
|
+
HELP_BANNER = <<-HELP
|
10
|
+
Sesh: remote background sessions powered by tmux and tmuxinator.
|
11
|
+
Runs a headless tmuxinator session for remote slave machines to connect to.
|
12
|
+
|
13
|
+
Usage: #{File.basename $0} command [project]
|
14
|
+
|
15
|
+
Commands:
|
16
|
+
|
17
|
+
sesh new Create a new tmuxinator configuration.
|
18
|
+
sesh edit [project] Edit an existing tmuxinator configuration.
|
19
|
+
sesh start [project] Start a Sesh session for a project.
|
20
|
+
sesh stop [project] Stop a Sesh session for a project.
|
21
|
+
sesh list List running Sesh sessions on this machine.
|
22
|
+
sesh enslave [project] user@host Connect a slave to a local Sesh session.
|
23
|
+
sesh connect [project] [user@host] Connect as a slave to a Sesh session.
|
24
|
+
sesh run [location] [command] Run a shell command in the specified location.
|
25
|
+
sesh rspec [location] [spec] Run a spec in the specified location.
|
26
|
+
|
27
|
+
Leave project blank to use the name of your current directory.
|
28
|
+
|
29
|
+
HELP
|
30
|
+
|
31
|
+
DEFAULT_OPTIONS = {
|
32
|
+
project: nil,
|
33
|
+
ssh: {
|
34
|
+
local_addr: nil,
|
35
|
+
remote_addr: nil
|
36
|
+
},
|
37
|
+
tmux: {
|
38
|
+
socket_file: nil,
|
39
|
+
pids_file: nil
|
40
|
+
}
|
41
|
+
}
|
42
|
+
POSSIBLE_CONFIG_LOCATIONS = %w( sesh_config.yml config/sesh.yml )
|
43
|
+
|
44
|
+
def self.format_command(command) command.gsub(/\ [ ]+/, ' ').strip end
|
45
|
+
def self.format_and_run_command(command) `#{format_command(command)}`.strip end
|
46
|
+
end
|
data/sesh.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "sesh/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'sesh'
|
8
|
+
s.version = Sesh::VERSION
|
9
|
+
s.date = '2015-03-17'
|
10
|
+
s.summary = "Sesh"
|
11
|
+
s.description = "Remote background sessions powered by tmux and tmuxinator."
|
12
|
+
s.authors = ["MacKinley Smith"]
|
13
|
+
s.email = 'smithmackinley@gmail.com'
|
14
|
+
# s.files = Dir["lib/**/*", "spec/**/*", "bin/*"]
|
15
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
s.bindir = "exe"
|
17
|
+
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
# s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.homepage = 'http://rubygems.org/gems/sesh'
|
22
|
+
s.license = 'GNU GPLv3'
|
23
|
+
|
24
|
+
s.add_development_dependency "bundler", "~> 1.10"
|
25
|
+
s.add_development_dependency "rake", "~> 10.0"
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
|
28
|
+
s.add_dependency 'tmuxinator', '~> 0.6.9'
|
29
|
+
s.add_dependency 'deep_merge'
|
30
|
+
s.add_dependency 'awesome_print'
|
31
|
+
s.add_dependency 'colorize'
|
32
|
+
end
|