tmux-cssh 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +0 -0
- data/README.md +80 -0
- data/README_ja.md +99 -0
- data/Rakefile +0 -0
- data/VERSION +1 -0
- data/bin/tssh +104 -0
- data/lib/tssh.rb +72 -0
- data/tmux-cssh.gemspec +13 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5e98cc319a8b74ce0ef895d7ca295621028ae6e6
|
4
|
+
data.tar.gz: 0dab99e91d4471541ecaa78269acf97ad8d26525
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70ec48338850dc68cad71debe431be1cfd0ae530f1d57ab10d9cba7d91a5cf21c40b5c64f617c250795f8b37f3369cd7da73c518220b7c3dd3504c9abca3f506
|
7
|
+
data.tar.gz: 03e40a19d8af851efc3bac78a26b05219abf2f2a770a9546a9d263574a70031a9d05741a2c64d154595ba633cdf855adbca35a9c7ad02f95c46fb7ff8c85c883
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# tmux-cssh-rb
|
2
|
+
|
3
|
+
## Requirements
|
4
|
+
|
5
|
+
* Ruby 1.9.3 or later
|
6
|
+
* tmux
|
7
|
+
|
8
|
+
## Installing
|
9
|
+
|
10
|
+
### Install from RubyGems repository
|
11
|
+
|
12
|
+
coming soon...
|
13
|
+
|
14
|
+
### Build gem and install
|
15
|
+
|
16
|
+
```sh
|
17
|
+
git clone https://github.com/yshh/tmux-cssh-rb.git path/to/tmux-cssh-rb
|
18
|
+
cd path/to/tmux-cssh-rb
|
19
|
+
gem build tmux-cssh.gemspec
|
20
|
+
gem install tmux-cssh-*.gem
|
21
|
+
```
|
22
|
+
|
23
|
+
### Use Bundler
|
24
|
+
|
25
|
+
Write the line below to your Gemfile:
|
26
|
+
```ruby
|
27
|
+
gem 'tmux-cssh', git: 'https://github.com/yshh/tmux-cssh-rb.git'
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
```
|
33
|
+
% tssh -h
|
34
|
+
Usage: tssh [options]
|
35
|
+
-l, --login USERNAME
|
36
|
+
-c, --config FILE
|
37
|
+
--ssh SSH_COMMAND
|
38
|
+
--ssh_args SSH_ARGUMENTS
|
39
|
+
--debug DEBUG_LEVEL
|
40
|
+
--panes_per_window NUM
|
41
|
+
```
|
42
|
+
|
43
|
+
`tssh` should be executed outside of tmux session.
|
44
|
+
|
45
|
+
```sh
|
46
|
+
tssh user@host1 host2 host3
|
47
|
+
```
|
48
|
+
|
49
|
+
### Sync mode
|
50
|
+
|
51
|
+
When you type to pane, key strokes are copied to all panes in the window.
|
52
|
+
Use tmux command `set-window-option synchronize-panes` to disable this mode.
|
53
|
+
|
54
|
+
You may want to bind a shortcut key to this command (add the line below to your ~/.tmux.conf)
|
55
|
+
|
56
|
+
```
|
57
|
+
bind-key g setw synchronize-panes
|
58
|
+
```
|
59
|
+
|
60
|
+
### How to exit
|
61
|
+
|
62
|
+
Panes do not close after the ssh session terminates
|
63
|
+
(using `set remain-on-exit`) so that you can see error messages in the case
|
64
|
+
ssh session terminated unexpectedly by errors such as connection error and
|
65
|
+
authentication failure.
|
66
|
+
|
67
|
+
You can close dead panes with tmux commands:
|
68
|
+
|
69
|
+
* `kill-pane` (closes selected pane; bound to `C-b x` by default) or
|
70
|
+
* `kill-window` (closes all panes in current window; bound to `C-b &` by default)
|
71
|
+
|
72
|
+
## OS limits
|
73
|
+
|
74
|
+
Opening many tmux panes hits some OS limits.
|
75
|
+
|
76
|
+
* num of pty
|
77
|
+
* max open files
|
78
|
+
|
79
|
+
## Known issues
|
80
|
+
|
data/README_ja.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# tmux-cssh-rb
|
2
|
+
|
3
|
+
## 必要環境
|
4
|
+
|
5
|
+
* Ruby 1.9.3 以上
|
6
|
+
* tmux
|
7
|
+
|
8
|
+
## インストール
|
9
|
+
|
10
|
+
### gem コマンド
|
11
|
+
|
12
|
+
TODO: RubyGems リポジトリに上げる
|
13
|
+
|
14
|
+
GitHub リポジトリから直接インストールする場合は以下を実行してください。
|
15
|
+
|
16
|
+
```sh
|
17
|
+
gem install specific_install
|
18
|
+
gem specific_install https://github.com/yshh/tmux-cssh-rb.git
|
19
|
+
```
|
20
|
+
|
21
|
+
### ビルド・インストール
|
22
|
+
|
23
|
+
```sh
|
24
|
+
git clone https://github.com/yshh/tmux-cssh-rb.git path/to/tmux-cssh-rb
|
25
|
+
cd path/to/tmux-cssh-rb
|
26
|
+
gem build tmux-cssh.gemspec
|
27
|
+
gem install tmux-cssh-*.gem
|
28
|
+
```
|
29
|
+
|
30
|
+
### Bundler を使う
|
31
|
+
|
32
|
+
Gemfile に以下を1行追加してください。
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'tmux-cssh', git: 'https://github.com/yshh/tmux-cssh-rb.git'
|
36
|
+
```
|
37
|
+
|
38
|
+
## 使い方
|
39
|
+
|
40
|
+
`tssh` は tmux セッションの外で実行する必要があります。
|
41
|
+
tmux セッション内での実行は現状サポート(?)外です。
|
42
|
+
|
43
|
+
```sh
|
44
|
+
tssh user@host1 host2 host3
|
45
|
+
```
|
46
|
+
|
47
|
+
`-h` オプションをつけて実行するとヘルプが表示されます。
|
48
|
+
|
49
|
+
```
|
50
|
+
% tssh -h
|
51
|
+
Usage: tssh [options]
|
52
|
+
-l, --login USERNAME
|
53
|
+
-c, --config FILE
|
54
|
+
--ssh SSH_COMMAND
|
55
|
+
--ssh_args SSH_ARGUMENTS
|
56
|
+
--debug DEBUG_LEVEL
|
57
|
+
--panes_per_window NUM
|
58
|
+
```
|
59
|
+
|
60
|
+
### Sync モード
|
61
|
+
|
62
|
+
ひとつの pane にタイプした文字は、window 内の全ての pane にコピーされます。
|
63
|
+
これは tmux の機能を使っており、
|
64
|
+
tmux コマンド `set-window-option synchronize-panes` で無効化できます。
|
65
|
+
この tmux コマンドにショートカットキーを割り当てると便利です。
|
66
|
+
|
67
|
+
例えば以下の tmux コマンドを ~/.tmux.conf に追加する:
|
68
|
+
|
69
|
+
```
|
70
|
+
bind-key g setw synchronize-panes
|
71
|
+
```
|
72
|
+
|
73
|
+
詳細については tmux のマニュアルを参照してください。
|
74
|
+
|
75
|
+
### 終了方法
|
76
|
+
|
77
|
+
pane は ssh セッションの終了後に自動的には終了しません
|
78
|
+
(tmux の `set-window-option remain-on-exit` を設定しています)。
|
79
|
+
認証えらーなどでセッションが意図せずエラー終了した際、エラーメッセージが
|
80
|
+
消えてしまうのを防ぐためです。
|
81
|
+
|
82
|
+
pane は以下の tmux コマンドで閉じることができます。
|
83
|
+
|
84
|
+
* `kill-pane` (選択中の pane を閉じる; デフォルト: `C-b x`)
|
85
|
+
* `kill-window` (window 上の全ての pane を閉じる; デフォルト: `C-b &`)
|
86
|
+
|
87
|
+
## OS の制限
|
88
|
+
|
89
|
+
たくさんの tmux pane を開くと、OS の各種上限に当たります。
|
90
|
+
|
91
|
+
* pty 数
|
92
|
+
* max open files
|
93
|
+
|
94
|
+
TODO: 回避方法
|
95
|
+
|
96
|
+
## 既知の問題
|
97
|
+
|
98
|
+
XXX
|
99
|
+
|
data/Rakefile
ADDED
File without changes
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/tssh
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'inifile'
|
5
|
+
require 'optparse'
|
6
|
+
require 'tssh'
|
7
|
+
|
8
|
+
# default options
|
9
|
+
options = {
|
10
|
+
login: nil,
|
11
|
+
config: "#{ENV['HOME']}/.tsshrc",
|
12
|
+
# tile_x: nil,
|
13
|
+
# tile_y: nil,
|
14
|
+
# ssh: '/usr/local/bin/proxychains4 ssh',
|
15
|
+
ssh: 'ssh',
|
16
|
+
ssh_args: '',
|
17
|
+
# remote_command: nil,
|
18
|
+
# hosts: nil,
|
19
|
+
# session_max: 256,
|
20
|
+
# ping_test: nil,
|
21
|
+
# ping_timeout: nil,
|
22
|
+
# sock: nil,
|
23
|
+
# sorthosts: false,
|
24
|
+
# slave_settings_set: nil,
|
25
|
+
# master_settings_set: nil,
|
26
|
+
# interleave: nil,
|
27
|
+
debug: 0,
|
28
|
+
|
29
|
+
panes_per_window: 128,
|
30
|
+
}
|
31
|
+
|
32
|
+
args = {}
|
33
|
+
|
34
|
+
ARGV.options do |opt|
|
35
|
+
opt.on('-l', '--login USERNAME',
|
36
|
+
"SSH login name (default: #{options[:login]})"
|
37
|
+
) { |v| args[:login] = v }
|
38
|
+
opt.on('-c', '--config FILE',
|
39
|
+
"Config file path (default: #{options[:config]})"
|
40
|
+
) { |v|
|
41
|
+
raise "Config file '#{v}' does not exist" unless File.exists?(v)
|
42
|
+
args[:config] = v
|
43
|
+
}
|
44
|
+
opt.on('--ssh SSH_COMMAND',
|
45
|
+
"Custom SSH command (default: #{options[:config]})"
|
46
|
+
) { |v| args[:ssh] = v }
|
47
|
+
opt.on('--ssh_args SSH_ARGUMENTS') do |v|
|
48
|
+
args[:ssh_args] = v
|
49
|
+
end
|
50
|
+
opt.on('--debug DEBUG_LEVEL',
|
51
|
+
"Debug level [0-2] (default: #{options[:debug]})"
|
52
|
+
) { |v| args[:debug] = v.to_i }
|
53
|
+
opt.on("--panes_per_window NUM",
|
54
|
+
"Max panes opened on one window (default: #{options[:panes_per_window]})"
|
55
|
+
) { |v| args[:panes_per_window] = v.to_i }
|
56
|
+
opt.parse!
|
57
|
+
end
|
58
|
+
|
59
|
+
logger = Logger.new(STDERR)
|
60
|
+
|
61
|
+
# args "debug" and "config" are specially concerned
|
62
|
+
unless args[:debug].nil?
|
63
|
+
options[:debug] = args[:debug]
|
64
|
+
end
|
65
|
+
logger.level = case options[:debug]
|
66
|
+
when 0 then Logger::WARN
|
67
|
+
when 1 then Logger::INFO
|
68
|
+
when 2 then Logger::DEBUG
|
69
|
+
else raise "Invalid debug level: '#{options[:debug]}'"
|
70
|
+
end
|
71
|
+
|
72
|
+
unless args[:config].nil?
|
73
|
+
options[:config] = args[:config]
|
74
|
+
end
|
75
|
+
|
76
|
+
if not options[:config].nil? and File.exists?(options[:config])
|
77
|
+
logger.info "Using config file: #{options[:config]}"
|
78
|
+
# config file options overwrite the default options
|
79
|
+
config = IniFile.load(options[:config])
|
80
|
+
config['global'].each do |k, v|
|
81
|
+
# XXX
|
82
|
+
if %w(panes_per_window debug).include? k
|
83
|
+
options[k.to_sym] = v.to_i
|
84
|
+
else
|
85
|
+
options[k.to_sym] = v
|
86
|
+
end
|
87
|
+
logger.debug "option from config file: #{k} => #{v}"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
logger.info "Config file #{options[:config]} does not exist"
|
91
|
+
end
|
92
|
+
|
93
|
+
# command_line args are the most prioritized
|
94
|
+
args.each do |k, v|
|
95
|
+
logger.debug "option from args: #{k} => #{v}"
|
96
|
+
options[k] = v
|
97
|
+
end
|
98
|
+
|
99
|
+
# XXX
|
100
|
+
hosts = ARGV
|
101
|
+
|
102
|
+
tssh = TmuxClusterSSH.new(options, hosts, logger)
|
103
|
+
tssh.run
|
104
|
+
|
data/lib/tssh.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class TmuxClusterSSH
|
2
|
+
def initialize options, hosts, logger
|
3
|
+
@options = options
|
4
|
+
@hosts = hosts
|
5
|
+
@logger = logger
|
6
|
+
raise "No hosts specified" if @hosts.count == 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def ssh_command host
|
10
|
+
if @options[:login].nil?
|
11
|
+
"#{@options[:ssh]} #{@options[:ssh_args]} #{host}"
|
12
|
+
else
|
13
|
+
"#{@options[:ssh]} #{@options[:ssh_args]} #{@options[:login]}@#{host}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def calc_num_panes(num)
|
18
|
+
# XXX should read tmux implementation
|
19
|
+
if num > @options[:panes_per_window]
|
20
|
+
return @options[:panes_per_window] + calc_num_panes(num - @options[:panes_per_window])
|
21
|
+
end
|
22
|
+
|
23
|
+
sqrt_num = Math.sqrt(num)
|
24
|
+
panes = sqrt_num.ceil * sqrt_num.floor
|
25
|
+
panes >= num ? panes : sqrt_num.ceil ** 2
|
26
|
+
end
|
27
|
+
|
28
|
+
def run_command cmd
|
29
|
+
@logger.info cmd
|
30
|
+
res = system cmd
|
31
|
+
case res
|
32
|
+
when nil
|
33
|
+
raise "command `#{cmd}` failed"
|
34
|
+
when false
|
35
|
+
raise "command `#{cmd}` exited with status #{$?}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
num_panes = calc_num_panes(@hosts.count)
|
41
|
+
session_name = "tssh-#{$$}"
|
42
|
+
(0..num_panes-1).each do |i|
|
43
|
+
host = @hosts[i]
|
44
|
+
if host.nil?
|
45
|
+
command = "'echo;echo empty'"
|
46
|
+
else
|
47
|
+
command = "'echo #{host};exec #{ssh_command(host)}'"
|
48
|
+
end
|
49
|
+
STDERR.puts command if @options[:debug] > 0
|
50
|
+
if i == 0
|
51
|
+
# create new session
|
52
|
+
run_command "tmux new -d -s #{session_name} #{command}"
|
53
|
+
run_command "tmux setw -t #{session_name} synchronize-panes on > /dev/null"
|
54
|
+
run_command "tmux setw -t #{session_name} remain-on-exit on > /dev/null"
|
55
|
+
elsif i % @options[:panes_per_window] == 0
|
56
|
+
# create new window on existing session
|
57
|
+
run_command "tmux neww -t #{session_name} #{command}"
|
58
|
+
run_command "tmux setw -t #{session_name} synchronize-panes on > /dev/null"
|
59
|
+
run_command "tmux setw -t #{session_name} remain-on-exit on > /dev/null"
|
60
|
+
else
|
61
|
+
# add pane to active window
|
62
|
+
run_command "tmux splitw -t #{session_name} #{command}"
|
63
|
+
run_command "tmux selectl -t #{session_name} tiled > /dev/null"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# XXX
|
67
|
+
run_command 'tmux select-pane -t 0'
|
68
|
+
|
69
|
+
# attach to the session
|
70
|
+
exec "tmux attach -t #{session_name}"
|
71
|
+
end
|
72
|
+
end
|
data/tmux-cssh.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'tmux-cssh'
|
3
|
+
s.version = '0.2.1'
|
4
|
+
s.licenses = ['MIT']
|
5
|
+
s.summary = 'Cluster-SSH on tmux'
|
6
|
+
s.homepage = 'https://github.com/yshh/tmux-cssh-rb'
|
7
|
+
s.authors = ['Yusuke Hagihara']
|
8
|
+
s.email = 'yusuke.hagihara@gmail.com'
|
9
|
+
s.files = `git ls-files`.split($/)
|
10
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
11
|
+
s.add_dependency 'inifile', '~> 2.0'
|
12
|
+
s.required_ruby_version = '>= 1.9.3'
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tmux-cssh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yusuke Hagihara
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: inifile
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
description:
|
28
|
+
email: yusuke.hagihara@gmail.com
|
29
|
+
executables:
|
30
|
+
- tssh
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- README_ja.md
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- bin/tssh
|
42
|
+
- lib/tssh.rb
|
43
|
+
- tmux-cssh.gemspec
|
44
|
+
homepage: https://github.com/yshh/tmux-cssh-rb
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.9.3
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.2.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Cluster-SSH on tmux
|
68
|
+
test_files: []
|