rosh 0.9.3 → 0.9.5
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/Makefile +6 -0
- data/README.md +1 -0
- data/lib/rosh/version.rb +1 -1
- data/lib/rosh.rb +38 -10
- data/rosh.gemspec +1 -0
- data/test/rosh_forwarding_test.rb +58 -2
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a9b04489aa21bdb87dd7a21a2696d33c0f1b29c45767593bca5bdc0b383b2a4
|
4
|
+
data.tar.gz: a43d640d729094bb85640cfece07ebe2f1b82bb007723d528b628fb5a495a23a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35972089d8384a358f3e79795fbf774be00e38b811872f690491057ccb17134488d3a19d21cdd2bc5854acab669033273689f2d99bc1fedbdb324d1eff31982c
|
7
|
+
data.tar.gz: 8a422f7936c8482d12b168ed6a5aab6dddda8691e98b327c206a676976f770ad3a02d4a0d19888eeb86188b0dea4431b370ed2e6989559758d99adaf2c696932
|
data/Makefile
ADDED
data/README.md
CHANGED
@@ -26,6 +26,7 @@ Or install it yourself as:
|
|
26
26
|
Default: "^t"
|
27
27
|
-I interval Reconnection interval.
|
28
28
|
-S Use GNU screen instead of tmux
|
29
|
+
-L socket-name Specify tmux socket name (tmux -L socket-name)
|
29
30
|
|
30
31
|
If ~/.ssh/config contains LocalForward or RemoteForward for the host, the same
|
31
32
|
forwarding options are passed to `ssh` automatically.
|
data/lib/rosh/version.rb
CHANGED
data/lib/rosh.rb
CHANGED
@@ -11,6 +11,7 @@ class Rosh
|
|
11
11
|
@ssh_opts = []
|
12
12
|
alive_interval = 5
|
13
13
|
@escape = '^t'
|
14
|
+
@tmux_socket_name = nil
|
14
15
|
OptionParser.new("test").tap do |opt|
|
15
16
|
opt.banner = 'Usage: rosh [options] hostname [session-name]'
|
16
17
|
opt.on('-a alive-interval'){|v| alive_interval = v.to_i}
|
@@ -18,6 +19,7 @@ class Rosh
|
|
18
19
|
opt.on('-I interval'){|v| @interval = v.to_f}
|
19
20
|
opt.on('-V'){|v| @verbose = true}
|
20
21
|
opt.on('-S'){|v| @screen = true}
|
22
|
+
opt.on('-L socket-name'){|v| @tmux_socket_name = v}
|
21
23
|
end.parse! args
|
22
24
|
@host, @name = *args, :default
|
23
25
|
abort 'hostname is required' if @host == :default
|
@@ -62,8 +64,9 @@ class Rosh
|
|
62
64
|
["ssh", *@ssh_opts, resolv,
|
63
65
|
'-t', "'screen -rx #{@name}'", '2>/dev/null']*' '
|
64
66
|
else
|
67
|
+
remote_cmd = single_quote(tmux_attach_command)
|
65
68
|
["ssh", *@ssh_opts, resolv,
|
66
|
-
'-t',
|
69
|
+
'-t', remote_cmd, '2>/dev/null']*' '
|
67
70
|
end
|
68
71
|
if @verbose
|
69
72
|
puts "connecting to #{@host}..."
|
@@ -227,23 +230,25 @@ private
|
|
227
230
|
|
228
231
|
def sh_has_session?
|
229
232
|
# tmux has-session -t <session_name>
|
230
|
-
|
231
|
-
"ssh",
|
232
|
-
*@ssh_opts,
|
233
|
-
resolv,
|
234
|
-
"'tmux has-session -t #{@name} 2>/dev/null'"
|
235
|
-
]*' '
|
236
|
-
puts cmd if @verbose
|
237
|
-
system cmd
|
233
|
+
ssh_tmux("#{tmux_prefix} has-session -t #{tmux_session_name} 2>/dev/null")
|
238
234
|
end
|
239
235
|
|
240
236
|
def sh_new_session?
|
241
237
|
# tmux new-session -s <session_name> -d
|
238
|
+
create_with_override = "#{tmux_prefix} new-session -s #{tmux_session_name} -d \\; set-option -t #{tmux_session_name} destroy-unattached off"
|
239
|
+
return true if ssh_tmux(create_with_override)
|
240
|
+
|
241
|
+
puts "retrying tmux new-session without destroy-unattached override" if @verbose
|
242
|
+
ssh_tmux("#{tmux_prefix} new-session -s #{tmux_session_name} -d")
|
243
|
+
end
|
244
|
+
|
245
|
+
def ssh_tmux(command)
|
246
|
+
remote_command = single_quote(command.to_s)
|
242
247
|
cmd = [
|
243
248
|
"ssh",
|
244
249
|
*@ssh_opts,
|
245
250
|
resolv,
|
246
|
-
|
251
|
+
remote_command
|
247
252
|
]*' '
|
248
253
|
puts cmd if @verbose
|
249
254
|
system cmd
|
@@ -292,4 +297,27 @@ private
|
|
292
297
|
rescue Exception
|
293
298
|
@host
|
294
299
|
end
|
300
|
+
|
301
|
+
def tmux_attach_command
|
302
|
+
"#{tmux_prefix} attach -t #{tmux_session_name}"
|
303
|
+
end
|
304
|
+
|
305
|
+
def tmux_prefix
|
306
|
+
return 'tmux' unless @tmux_socket_name
|
307
|
+
"tmux -L #{format_remote_arg(@tmux_socket_name)}"
|
308
|
+
end
|
309
|
+
|
310
|
+
def tmux_session_name
|
311
|
+
format_remote_arg(@name)
|
312
|
+
end
|
313
|
+
|
314
|
+
def format_remote_arg(value)
|
315
|
+
str = value.to_s
|
316
|
+
return str if str.match?(%r{\A[[:alnum:]@%+=:,./_-]+\z})
|
317
|
+
%("#{str.gsub(/(["\\$`])/, '\\\\\1')}")
|
318
|
+
end
|
319
|
+
|
320
|
+
def single_quote(str)
|
321
|
+
"'" + str.to_s.gsub("'", %q('\'') ) + "'"
|
322
|
+
end
|
295
323
|
end
|
data/rosh.gemspec
CHANGED
@@ -24,14 +24,24 @@ class RoshForwardingTest < Minitest::Test
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_ssh_opts_include_local_forwarding_from_ssh_config
|
27
|
-
|
27
|
+
klass = Class.new(Rosh) do
|
28
|
+
def local_forward_available?(_spec)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
rosh = klass.new('grav')
|
28
33
|
ssh_opts = rosh.instance_variable_get(:@ssh_opts)
|
29
34
|
|
30
35
|
assert_includes ssh_opts, '-L 127.0.0.1:3131:localhost:3131'
|
31
36
|
end
|
32
37
|
|
33
38
|
def test_ssh_opts_include_remote_forwarding_from_ssh_config
|
34
|
-
|
39
|
+
klass = Class.new(Rosh) do
|
40
|
+
def local_forward_available?(_spec)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rosh = klass.new('grav')
|
35
45
|
ssh_opts = rosh.instance_variable_get(:@ssh_opts)
|
36
46
|
|
37
47
|
assert_includes ssh_opts, '-R 8082:localhost:8082'
|
@@ -84,4 +94,50 @@ class RoshForwardingTest < Minitest::Test
|
|
84
94
|
out_again, _ = capture_io { rosh.send(:report_oom_if_needed, 'ssh grav') }
|
85
95
|
assert_equal '', out_again
|
86
96
|
end
|
97
|
+
|
98
|
+
def test_tmux_new_session_disables_destroy_unattached
|
99
|
+
rosh = Rosh.new('grav', 'grav')
|
100
|
+
commands = []
|
101
|
+
rosh.stub(:system, ->(cmd) { commands << cmd; true }) do
|
102
|
+
assert rosh.send(:sh_new_session?)
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal 1, commands.size
|
106
|
+
assert_includes commands.first, 'set-option -t grav destroy-unattached off'
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_tmux_new_session_fallback_without_override
|
110
|
+
rosh = Rosh.new('grav', 'grav')
|
111
|
+
commands = []
|
112
|
+
results = [false, true].each
|
113
|
+
|
114
|
+
rosh.stub(:system, ->(cmd) { commands << cmd; results.next rescue true }) do
|
115
|
+
assert rosh.send(:sh_new_session?)
|
116
|
+
end
|
117
|
+
|
118
|
+
assert_includes commands.first, 'set-option -t grav destroy-unattached off'
|
119
|
+
refute_includes commands.last, 'set-option -t grav destroy-unattached off'
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_tmux_commands_include_socket_name_when_specified
|
123
|
+
socket_name = 'work'
|
124
|
+
rosh = Rosh.new('-L', socket_name, 'grav')
|
125
|
+
rosh.instance_variable_set(:@name, 'grav')
|
126
|
+
|
127
|
+
assert_includes rosh.send(:tmux_attach_command), "-L #{socket_name}"
|
128
|
+
|
129
|
+
commands = []
|
130
|
+
rosh.stub(:system, ->(cmd) { commands << cmd; true }) do
|
131
|
+
rosh.send(:sh_has_session?)
|
132
|
+
end
|
133
|
+
|
134
|
+
assert_includes commands.first, "-L #{socket_name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_tmux_default_socket_used_when_not_specified
|
138
|
+
rosh = Rosh.new('grav')
|
139
|
+
command = rosh.send(:tmux_attach_command)
|
140
|
+
|
141
|
+
refute_includes command, '-L'
|
142
|
+
end
|
87
143
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rosh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Genki Takiuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -65,6 +79,7 @@ files:
|
|
65
79
|
- ".gitignore"
|
66
80
|
- Gemfile
|
67
81
|
- LICENSE.txt
|
82
|
+
- Makefile
|
68
83
|
- README.md
|
69
84
|
- Rakefile
|
70
85
|
- bin/rosh
|