hetzner-bootstrap 0.0.1 → 0.0.2
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.
- data/README.rdoc +32 -0
- data/example.rb +1 -0
- data/lib/hetzner-bootstrap.rb +20 -34
- data/lib/hetzner/bootstrap/target.rb +73 -30
- data/lib/hetzner/bootstrap/version.rb +1 -1
- metadata +5 -5
- data/README +0 -9
data/README.rdoc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
= hetzner-bootstrap
|
2
|
+
|
3
|
+
hetzner-bootstrap allows you to bootstrap a provisioned EQ Server from hetzner.de
|
4
|
+
|
5
|
+
Requirements
|
6
|
+
|
7
|
+
- get a webservice login from your customer panel (https://robot.your-server.de/)
|
8
|
+
- the ip address of the shipped system(s)
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
|
12
|
+
<tt> gem install hetzner-bootstrap</tt>
|
13
|
+
|
14
|
+
== Example
|
15
|
+
|
16
|
+
<b>see example.rb for usage!</b>
|
17
|
+
|
18
|
+
== WARNING!
|
19
|
+
|
20
|
+
This is not an official Hetzner AG project.
|
21
|
+
|
22
|
+
The gem and the author are not related to Hetzner AG!
|
23
|
+
|
24
|
+
<b>Use at your very own risk! Satisfaction NOT guaranteed!</b>
|
25
|
+
|
26
|
+
== Copyright
|
27
|
+
|
28
|
+
Copyright (c) 2011 Moriz GmbH, Roland Moriz. See LICENSE for details.
|
29
|
+
|
30
|
+
{Ruby on Rails Entwicklung}[http://moriz.de/] -> Moriz GmbH
|
31
|
+
|
32
|
+
{Ruby on Rails Hosting}[http://rails.io/] -> Rails.io
|
data/example.rb
CHANGED
data/lib/hetzner-bootstrap.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'benchmark'
|
2
|
+
require 'logger'
|
2
3
|
|
3
4
|
require 'hetzner-api'
|
4
5
|
require 'hetzner/bootstrap/version'
|
@@ -9,21 +10,25 @@ module Hetzner
|
|
9
10
|
class Bootstrap
|
10
11
|
attr_accessor :targets
|
11
12
|
attr_accessor :api
|
12
|
-
attr_accessor :use_threads
|
13
13
|
attr_accessor :actions
|
14
|
+
attr_accessor :logger
|
14
15
|
|
15
16
|
def initialize(options = {})
|
16
17
|
@targets = []
|
17
18
|
@actions = %w(enable_rescue_mode
|
18
19
|
reset
|
19
|
-
|
20
|
+
wait_for_ssh_down
|
21
|
+
wait_for_ssh_up
|
20
22
|
installimage
|
21
|
-
|
23
|
+
reboot
|
24
|
+
wait_for_ssh_down
|
25
|
+
wait_for_ssh_up
|
22
26
|
verify_installation
|
23
27
|
copy_ssh_keys
|
24
28
|
post_install)
|
29
|
+
#@actions = %w(wait_for_ssh_down)
|
25
30
|
@api = options[:api]
|
26
|
-
@
|
31
|
+
@logger = options[:logger] || Logger.new(STDOUT)
|
27
32
|
end
|
28
33
|
|
29
34
|
def add_target(param)
|
@@ -37,50 +42,31 @@ module Hetzner
|
|
37
42
|
def <<(param)
|
38
43
|
add_target param
|
39
44
|
end
|
40
|
-
|
41
|
-
def bootstrap!(options = {})
|
42
|
-
threads = []
|
43
45
|
|
46
|
+
def bootstrap!(options = {})
|
44
47
|
@targets.each do |target|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
threads << Thread.new do
|
49
|
-
bootstrap_one_target! target
|
50
|
-
end
|
51
|
-
else
|
48
|
+
#fork do
|
49
|
+
target.use_api @api
|
50
|
+
target.use_logger @logger
|
52
51
|
bootstrap_one_target! target
|
53
|
-
end
|
52
|
+
#end
|
54
53
|
end
|
55
|
-
|
56
|
-
finalize_threads(threads) if uses_threads?
|
54
|
+
#Process.waitall
|
57
55
|
end
|
58
56
|
|
59
57
|
def bootstrap_one_target!(target)
|
60
58
|
actions = (target.actions || @actions)
|
61
59
|
actions.each_with_index do |action, index|
|
62
|
-
|
63
|
-
|
60
|
+
|
61
|
+
loghack = "\b" * 24 # remove: "[bootstrap_one_target!] ".length
|
62
|
+
target.logger.info "#{loghack}[#{action}] #{sprintf "%-20s", "START"}"
|
64
63
|
d = Benchmark.realtime do
|
65
64
|
target.send action
|
66
65
|
end
|
67
|
-
|
68
|
-
log target.ip, action, index, "FINISHED in #{sprintf "%.5f",d} seconds"
|
66
|
+
target.logger.info "#{loghack}[#{action}] FINISHED in #{sprintf "%.5f",d} seconds"
|
69
67
|
end
|
70
68
|
rescue => e
|
71
|
-
puts "something bad
|
72
|
-
end
|
73
|
-
|
74
|
-
def uses_threads?
|
75
|
-
@use_threads
|
76
|
-
end
|
77
|
-
|
78
|
-
def finalize_threads(threads)
|
79
|
-
threads.each { |t| t.join }
|
80
|
-
end
|
81
|
-
|
82
|
-
def log(where, what, index, message)
|
83
|
-
puts "[#{where}] #{what} #{' ' * (index * 4)}#{message}"
|
69
|
+
puts "something bad happened unexpectedly: #{e.class} => #{e.message}"
|
84
70
|
end
|
85
71
|
end
|
86
72
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'erubis'
|
2
2
|
require 'net/ssh'
|
3
3
|
require 'socket'
|
4
|
+
require 'timeout'
|
4
5
|
|
5
6
|
module Hetzner
|
6
7
|
class Bootstrap
|
@@ -16,13 +17,15 @@ module Hetzner
|
|
16
17
|
attr_accessor :post_install
|
17
18
|
attr_accessor :public_keys
|
18
19
|
attr_accessor :bootstrap_cmd
|
20
|
+
attr_accessor :logger
|
19
21
|
|
20
22
|
def initialize(options = {})
|
21
23
|
@rescue_os = 'linux'
|
22
24
|
@rescue_os_bit = '64'
|
23
25
|
@retries = 0
|
24
26
|
@bootstrap_cmd = '/root/.oldroot/nfs/install/installimage -a -c /tmp/template'
|
25
|
-
|
27
|
+
@login = 'root'
|
28
|
+
|
26
29
|
if tmpl = options.delete(:template)
|
27
30
|
@template = Template.new tmpl
|
28
31
|
else
|
@@ -38,19 +41,19 @@ module Hetzner
|
|
38
41
|
result = @api.enable_rescue! @ip, @rescue_os, @rescue_os_bit
|
39
42
|
|
40
43
|
if result.success? && result['rescue']
|
41
|
-
@login = 'root'
|
42
44
|
@password = result['rescue']['password']
|
43
45
|
reset_retries
|
44
|
-
|
46
|
+
logger.info "IP: #{ip} => password: #{@password}"
|
45
47
|
elsif @retries > 3
|
48
|
+
logger.error "rescue system could not be activated"
|
46
49
|
raise CantActivateRescueSystemError, result
|
47
50
|
else
|
48
51
|
@retries += 1
|
49
52
|
|
50
|
-
|
53
|
+
logger.warn "problem while trying to activate rescue system (retries: #{@retries})"
|
51
54
|
@api.disable_rescue! @ip
|
52
55
|
|
53
|
-
|
56
|
+
rolling_sleep
|
54
57
|
enable_rescue_mode options
|
55
58
|
end
|
56
59
|
end
|
@@ -60,37 +63,58 @@ module Hetzner
|
|
60
63
|
|
61
64
|
if result.success?
|
62
65
|
reset_retries
|
63
|
-
sleep 15
|
64
66
|
elsif @retries > 3
|
67
|
+
logger.error "resetting through webservice failed."
|
65
68
|
raise CantResetSystemError, result
|
66
69
|
else
|
67
70
|
@retries += 1
|
71
|
+
logger.warn "problem while trying to reset/reboot system (retries: #{@retries})"
|
68
72
|
rolling_sleep
|
69
|
-
puts "problem while trying to reset/reboot system (retries: #{@retries})"
|
70
73
|
reset options
|
71
74
|
end
|
72
75
|
end
|
73
76
|
|
74
|
-
def
|
75
|
-
ssh_port_probe = TCPSocket.new
|
76
|
-
|
77
|
+
def port_open? ip, port
|
78
|
+
ssh_port_probe = TCPSocket.new ip, port
|
79
|
+
IO.select([ssh_port_probe], nil, nil, 2)
|
80
|
+
ssh_port_probe.close
|
81
|
+
true
|
82
|
+
end
|
77
83
|
|
84
|
+
def wait_for_ssh_down(options = {})
|
85
|
+
loop do
|
86
|
+
Timeout::timeout(3) do
|
87
|
+
if port_open? @ip, 22
|
88
|
+
logger.debug "SSH UP"
|
89
|
+
sleep 10
|
90
|
+
else
|
91
|
+
raise Errno::ECONNREFUSED
|
92
|
+
end
|
93
|
+
end
|
94
|
+
sleep 2
|
95
|
+
end
|
96
|
+
rescue Timeout::Error
|
97
|
+
sleep 2
|
98
|
+
retry
|
78
99
|
rescue Errno::ECONNREFUSED
|
79
|
-
|
80
|
-
|
81
|
-
STDOUT.flush
|
100
|
+
logger.debug "SHH DOWN"
|
101
|
+
end
|
82
102
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
103
|
+
def wait_for_ssh_up(options = {})
|
104
|
+
loop do
|
105
|
+
Timeout::timeout(4) do
|
106
|
+
if port_open? @ip, 22
|
107
|
+
logger.debug "SSH UP"
|
108
|
+
return true
|
109
|
+
else
|
110
|
+
raise Errno::ECONNREFUSED
|
111
|
+
end
|
112
|
+
end
|
88
113
|
end
|
89
|
-
rescue
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
ssh_port_probe && ssh_port_probe.close
|
114
|
+
rescue Errno::ECONNREFUSED, Timeout::Error
|
115
|
+
logger.debug "SSH DOWN"
|
116
|
+
sleep 2
|
117
|
+
retry
|
94
118
|
end
|
95
119
|
|
96
120
|
def installimage(options = {})
|
@@ -98,11 +122,18 @@ module Hetzner
|
|
98
122
|
|
99
123
|
Net::SSH.start(@ip, @login, :password => @password) do |ssh|
|
100
124
|
ssh.exec!("echo \"#{template}\" > /tmp/template")
|
101
|
-
|
125
|
+
logger.info "remote executing: #{@bootstrap_cmd}"
|
102
126
|
output = ssh.exec!(@bootstrap_cmd)
|
103
|
-
|
127
|
+
logger.info output
|
128
|
+
end
|
129
|
+
rescue Net::SSH::HostKeyMismatch => e
|
130
|
+
e.remember_host!
|
131
|
+
retry
|
132
|
+
end
|
133
|
+
|
134
|
+
def reboot(options = {})
|
135
|
+
Net::SSH.start(@ip, @login, :password => @password) do |ssh|
|
104
136
|
ssh.exec!("reboot")
|
105
|
-
sleep 4
|
106
137
|
end
|
107
138
|
rescue Net::SSH::HostKeyMismatch => e
|
108
139
|
e.remember_host!
|
@@ -139,8 +170,8 @@ module Hetzner
|
|
139
170
|
def post_install(options = {})
|
140
171
|
return unless @post_install
|
141
172
|
post_install = render_post_install
|
142
|
-
|
143
|
-
|
173
|
+
logger.info "executing post_install:\n #{post_install}"
|
174
|
+
logger.info `#{post_install}`
|
144
175
|
end
|
145
176
|
|
146
177
|
def render_template
|
@@ -165,8 +196,13 @@ module Hetzner
|
|
165
196
|
return eruby.result(params)
|
166
197
|
end
|
167
198
|
|
168
|
-
def use_api(
|
169
|
-
@api =
|
199
|
+
def use_api(api_obj)
|
200
|
+
@api = api_obj
|
201
|
+
end
|
202
|
+
|
203
|
+
def use_logger(logger_obj)
|
204
|
+
@logger = logger_obj
|
205
|
+
@logger.formatter = default_log_formatter
|
170
206
|
end
|
171
207
|
|
172
208
|
def reset_retries
|
@@ -177,6 +213,13 @@ module Hetzner
|
|
177
213
|
sleep @retries * @retries * 3 + 1 # => 1, 4, 13, 28, 49, 76, 109, 148, 193, 244, 301, 364 ... seconds
|
178
214
|
end
|
179
215
|
|
216
|
+
def default_log_formatter
|
217
|
+
proc do |severity, datetime, progname, msg|
|
218
|
+
caller[4]=~/`(.*?)'/
|
219
|
+
"[#{datetime.strftime "%H:%M:%S"}][#{sprintf "%-15s", ip}][#{$1}] #{msg}\n"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
180
223
|
class NoTemplateProvidedError < ArgumentError; end
|
181
224
|
class CantActivateRescueSystemError < StandardError; end
|
182
225
|
class CantResetSystemError < StandardError; end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hetzner-bootstrap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Roland Moriz
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-11 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -89,7 +89,7 @@ files:
|
|
89
89
|
- .gitignore
|
90
90
|
- Gemfile
|
91
91
|
- LICENSE
|
92
|
-
- README
|
92
|
+
- README.rdoc
|
93
93
|
- Rakefile
|
94
94
|
- example.rb
|
95
95
|
- hetzner-bootstrap.gemspec
|