hetzner-boot-freebsd 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.ruby-gemset +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +107 -0
- data/Rakefile +11 -0
- data/example.rb +27 -0
- data/hetzner-boot-freebsd.gemspec +41 -0
- data/lib/hetzner-boot-freebsd.rb +66 -0
- data/lib/hetzner/boot/freebsd/target.rb +189 -0
- data/lib/hetzner/boot/freebsd/version.rb +8 -0
- data/spec/hetzner-boot-freebsd_spec.rb +28 -0
- data/spec/spec_helper.rb +6 -0
- metadata +299 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2583c2141ddbab2a3ad43db9ce74ced9a2dfd909
|
4
|
+
data.tar.gz: 80329d78b46220309c4692c03cc42abd79ed7951
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef722a1e4879c0d3e42ae4012553b9c04c102f05dc5d165a2e56430f822f1a3e5ad247cc1efbfcf8318c6d95d7085de15d1a7cb1c116bd2b0066f237ebbfc7a8
|
7
|
+
data.tar.gz: 5bf5340d6a04b73aeb3bf606a877b0b7890f2aff6eec3df908a6a01dea3e1b052ddc8cd25591f1fd46918144835509faf0e337dd14019b30d51e19f9f2923014
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.0
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2015 Asconix Systems AS, Christoph Pilka, http://asconix.com
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
10
|
+
|
11
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# hetzner-boot-freebsd
|
2
|
+
|
3
|
+
This gem provides a convenient way to boot a Hetzner root server into FreeBSD rescue environment which is based on [mfsBSD](http://mfsbsd.vx.sk). The mfsBSD project consists of multiple scripts that create a bootable ISO image that create a minimal FreeBSD installation.
|
4
|
+
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/hetzner-boot-freebsd.png)](http://badge.fury.io/rb/hetzner-boot-freebsd)
|
6
|
+
|
7
|
+
## What `hetzner-boot-freebsd` does
|
8
|
+
|
9
|
+
We are running a considerable number of [FreeBSD](http://www.freebsd.org) servers in production as well as for development. We usually run FreeBSD jail hosts on bare metal servers hosted at Hetzner and encapsulate all services into jails to get a separation of services and to increase the security of the entire setup. We use BSDploy, Ansible and Fabric to provision the jail hosts as well as the FreeBSD jails themselves.
|
10
|
+
|
11
|
+
An essential step to get the servers provisioned is to have a well-defined starting point which in our case is always a mfsBSD running on the server to be provisioned. Hetzner provides a FreeBSD rescue system that is based on mfsBSD which we use as a starting point to provision the server. This Ruby Gem offers a very convenient way to reboot a Hetzner root server into this rescue environment.
|
12
|
+
|
13
|
+
This Ruby Gem is basically a stripped down wrapper for the `hetzner-api` Gem with the focus on bringing up any Hetzner server into the FreeBSD rescue system.
|
14
|
+
|
15
|
+
## Implemented steps:
|
16
|
+
|
17
|
+
1. Enable FreeBSD rescue mode (using Hetzner's webservice)
|
18
|
+
2. Resetting the System to boot into rescue mode (using Hetzner's webservice)
|
19
|
+
3. Log into the rescue system and launch the installation
|
20
|
+
|
21
|
+
## Requirements
|
22
|
+
|
23
|
+
First of all retrieve your API login credentials from the Hetzner admin interface at [https://robot.your-server.de](https://robot.your-server.de). Additionally you need the IP address of the system that you want to provision.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
$~ gem install hetzner-boot-freebsd
|
28
|
+
|
29
|
+
The `hetzner-freebsd-boot` Ruby Gem requires the credentials for Hetzner's HTTP API to manage the server. These credentials must be available via the environment variables `HETZNER_ROBOT_USERNAME` and `HETZNER_USERNAME_PASSWORD`. You cen set the either in `~/.bash_profile`:
|
30
|
+
|
31
|
+
export HETZNER_ROBOT_USERNAME="MyHetznerUsername"
|
32
|
+
export HETZNER_ROBOT_PASSWORD="MyVerySecretHetznerPW"
|
33
|
+
|
34
|
+
or create a `.env` file in your Ruby project directory and define them there:
|
35
|
+
|
36
|
+
HETZNER_ROBOT_USERNAME="MyHetznerUsername"
|
37
|
+
HETZNER_ROBOT_PASSWORD="MyVerySecretHetznerPW"
|
38
|
+
|
39
|
+
## Example
|
40
|
+
|
41
|
+
See `example.rb` file for usage. The example requires valid credentials for the Hetzner HTTP API (see the both environment variables `HETZNER_ROBOT_USERNAME` and `HETZNER_ROBOT_PASSWORD` above) as well as a concrete target host to reboot (environment variables `HETZNER_ROBOT_IP_ADDRESS` and `HETZNER_ROBOT_HOSTNAME`). Export them either via your `~/.bash_profile`:
|
42
|
+
|
43
|
+
export HETZNER_ROBOT_IP_ADDRESS="1.2.3.4"
|
44
|
+
export HETZNER_ROBOT_HOSTNAME="freebsd.example com"
|
45
|
+
|
46
|
+
... or append them to the `.env` file within your project that will be read by `dotenv`:
|
47
|
+
|
48
|
+
HETZNER_ROBOT_IP_ADDRESS="1.2.3.4"
|
49
|
+
HETZNER_ROBOT_HOSTNAME="freebsd.example com"
|
50
|
+
|
51
|
+
Afterwards open a new shell and navigate into your project directory. All 4 environment variables should be now exposed for the example code.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
|
55
|
+
#!/usr/bin/env ruby
|
56
|
+
require "rubygems"
|
57
|
+
require "hetzner-boot-freebsd"
|
58
|
+
|
59
|
+
# Retrieve your API login credentials from the Hetzner admin interface
|
60
|
+
# at https://robot.your-server.de and assign the appropriate environment
|
61
|
+
# variables ENV['ROBOT_USER'] and ENV['ROBOT_PASSWORD']
|
62
|
+
|
63
|
+
bs = Hetzner::Boot::FreeBSD.new(:api => Hetzner::API.new(ENV['ROBOT_USER'], ENV['ROBOT_PASSWORD']))
|
64
|
+
|
65
|
+
# The post_install hook is the right place to launch further tasks (e.g.
|
66
|
+
# software installation, system provisioning etc.)
|
67
|
+
post_install = <<EOT
|
68
|
+
# TODO
|
69
|
+
EOT
|
70
|
+
|
71
|
+
bs << {:ip => "1.2.3.4",
|
72
|
+
:hostname => 'artemis.asconix.com',
|
73
|
+
:public_keys => "~/.ssh/id_rsa.pub",
|
74
|
+
:post_install => post_install
|
75
|
+
}
|
76
|
+
|
77
|
+
bs.bootstrap!
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
## Warnings:
|
82
|
+
|
83
|
+
* All existing data on the system will be wiped on bootstrap!
|
84
|
+
* This is not an official Hetzner AG project.
|
85
|
+
* The gem and the author are not related to Hetzner AG!
|
86
|
+
|
87
|
+
**Use at your very own risk. Satisfaction is NOT guaranteed.**
|
88
|
+
|
89
|
+
## Thank you greeting
|
90
|
+
|
91
|
+
This Ruby gem is inspired by the [hetzner-bootstrap](https://github.com/rmoriz/hetzner-bootstrap) gem and requires the underlying wrapper for the Hetzner server management API [hetzner-api](https://github.com/rmoriz/hetzner-api). Thus I want to thank [Roland Moriz](https://roland.io/developer) a lot for his great work!
|
92
|
+
|
93
|
+
## Cheat sheet
|
94
|
+
|
95
|
+
Hetzner provides a very powerfull HTTP API. Here are some simple use cases for this API:
|
96
|
+
|
97
|
+
*Put a server into rescue mode (here FreeBSD 64-bit)*:
|
98
|
+
|
99
|
+
$~ curl -u $ROBOT_USER:$ROBOT_PASSWORD https://robot-ws.your-server.de/boot/136.243.0.21/rescue -d 'os=freebsd&arch=64'
|
100
|
+
|
101
|
+
*Restart the server by hardware reset*:
|
102
|
+
|
103
|
+
$~ curl -u $ROBOT_USER:$ROBOT_PASSWORD https://robot-ws.your-server.de/reset/136.243.0.21 -d 'type=hw'
|
104
|
+
|
105
|
+
## Copyright
|
106
|
+
|
107
|
+
Copyright (c) 2015 Asconix Systems AS, Christoph Pilka, [http://asconix.com](http://asconix.com)
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
# Default directory to look within is `/specs`
|
5
|
+
# Run with `rake spec`
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
7
|
+
task.rspec_opts = ['--color', '--format', 'documentation']
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Run RSpec tests"
|
11
|
+
task :default => :spec
|
data/example.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'hetzner-boot-freebsd'
|
4
|
+
|
5
|
+
# Retrieve your API login credentials from the Hetzner admin interface
|
6
|
+
# at https://robot.your-server.de and assign the appropriate environment
|
7
|
+
# variables:
|
8
|
+
#
|
9
|
+
# $~ export HETZNER_ROBOT_USERNAME="MyHetznerUsername"
|
10
|
+
# $~ export HETZNER_ROBOT_PASSWORD="MyVerySecretPassword"
|
11
|
+
# $~ export HETZNER_ROBOT_IP_ADDRESS="1.2.3.4"
|
12
|
+
# $~ export HETZNER_ROBOT_HOSTNAME="freebsd.example.com"
|
13
|
+
#
|
14
|
+
# Next launch the bootstrap script:
|
15
|
+
#
|
16
|
+
# $~ ./example.rb
|
17
|
+
|
18
|
+
api = Hetzner::API.new ENV['HETZNER_ROBOT_USERNAME'], ENV['HETZNER_ROBOT_PASSWORD']
|
19
|
+
rescue_env = Hetzner::Boot::FreeBSD.new :api => @api
|
20
|
+
|
21
|
+
rescue_env << {
|
22
|
+
:ip => ENV['HETZNER_ROBOT_IP_ADDRESS'],
|
23
|
+
:hostname => ENV['HETZNER_ROBOT_HOSTNAME'],
|
24
|
+
:public_keys => 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAxpbPqgja8qK0pRBu423nuj7ZqJY/VPyABvBtcHQBpnaz20hSo89K+yEJmdg4upKk54906u7OT5tGaFpTYQKUxGgdKO1my8y2tXHDdTGw1A3BZotgIwDDvNTrIYW8JlGOBTVQuHGm6EYf8tEVut+dhueSe0VsK3keTQQwwatSf4uBgYxRMorsVWFVwk+YH2RKC25pbh0teoagL1TVts4OqGTcRJtrO9PHkuHFNCqA5IQVf+BRzwyCNWGaLuX3W/+DOOx3u76UhKBWrWXicVksFUD7tnFJrZohLu6PtKBoSSlVYVO/YgXQEJtsvG1EmEaoMnM2TvdzIWcopdd2jIo8Cw== c.pilka@asconix.com'
|
25
|
+
}
|
26
|
+
|
27
|
+
rescue_env.bootstrap!
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "hetzner/boot/freebsd/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hetzner-boot-freebsd"
|
8
|
+
spec.version = Hetzner::Boot::FreeBSD::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.authors = ["Christoph Pilka"]
|
11
|
+
spec.email = ["c.pilka@asconix.com"]
|
12
|
+
spec.summary = %q{Reboot a Hetzner server into FreeBSD rescue environment}
|
13
|
+
spec.description = %q{Allows to reboot a physical server hosted at Hetzner into a minimal FreeBSD recscue environment taht is based on mfsBSD.}
|
14
|
+
spec.homepage = "http://www.asconix.com"
|
15
|
+
spec.license = "BSD-3"
|
16
|
+
|
17
|
+
spec.add_dependency 'hetzner-api', '~> 1.1.0'
|
18
|
+
spec.add_dependency 'net-ssh', '~> 2.9.2'
|
19
|
+
spec.add_dependency 'erubis', '~> 2.7.0'
|
20
|
+
spec.add_dependency 'colorize', '~> 0.7.5'
|
21
|
+
spec.add_dependency 'highline', '~> 1.7.1'
|
22
|
+
spec.add_dependency 'dotenv', '~> 2.0.0'
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0")
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec"
|
32
|
+
spec.add_development_dependency "rspec-nc"
|
33
|
+
spec.add_development_dependency "guard"
|
34
|
+
spec.add_development_dependency "guard-rspec"
|
35
|
+
spec.add_development_dependency "pry"
|
36
|
+
spec.add_development_dependency "pry-remote"
|
37
|
+
spec.add_development_dependency "pry-nav"
|
38
|
+
spec.add_development_dependency "coveralls"
|
39
|
+
|
40
|
+
spec.add_runtime_dependency "crimp"
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
require 'hetzner-api'
|
5
|
+
require 'hetzner/boot/freebsd/version'
|
6
|
+
require 'hetzner/boot/freebsd/target'
|
7
|
+
|
8
|
+
module Hetzner
|
9
|
+
module Boot
|
10
|
+
class FreeBSD
|
11
|
+
attr_accessor :targets
|
12
|
+
attr_accessor :api
|
13
|
+
attr_accessor :actions
|
14
|
+
attr_accessor :logger
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
@targets = []
|
18
|
+
@actions = %w(
|
19
|
+
remove_from_local_known_hosts
|
20
|
+
enable_rescue_mode
|
21
|
+
reset
|
22
|
+
wait_for_ssh_down
|
23
|
+
wait_for_ssh_up
|
24
|
+
update_local_known_hosts
|
25
|
+
verify_installation
|
26
|
+
)
|
27
|
+
@api = options[:api]
|
28
|
+
@logger = options[:logger] || Logger.new(STDOUT)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_target(param)
|
32
|
+
if param.is_a? Hetzner::Boot::FreeBSD::Target
|
33
|
+
@targets << param
|
34
|
+
else
|
35
|
+
@targets << (Hetzner::Boot::FreeBSD::Target.new param)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def <<(param)
|
40
|
+
add_target param
|
41
|
+
end
|
42
|
+
|
43
|
+
def bootstrap!(options = {})
|
44
|
+
@targets.each do |target|
|
45
|
+
target.use_api @api
|
46
|
+
target.use_logger @logger
|
47
|
+
bootstrap_one_target! target
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def bootstrap_one_target!(target)
|
52
|
+
actions = (target.actions || @actions)
|
53
|
+
actions.each_with_index do |action, index|
|
54
|
+
loghack = "\b" * 24 # remove: "[bootstrap_one_target!] ".length
|
55
|
+
target.logger.info "#{loghack}[#{action}] #{sprintf "%-20s", "START"}"
|
56
|
+
d = Benchmark.realtime do
|
57
|
+
target.send action
|
58
|
+
end
|
59
|
+
target.logger.info "#{loghack}[#{action}] FINISHED in #{sprintf "%.5f",d} seconds"
|
60
|
+
end
|
61
|
+
rescue => e
|
62
|
+
target.logger.error "Something bad happened unexpectedly: #{e.class} => #{e.message}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'socket'
|
4
|
+
require 'timeout'
|
5
|
+
require 'colorize'
|
6
|
+
|
7
|
+
module Hetzner
|
8
|
+
module Boot
|
9
|
+
class FreeBSD
|
10
|
+
class Target
|
11
|
+
attr_accessor :ip
|
12
|
+
attr_accessor :login
|
13
|
+
attr_accessor :password
|
14
|
+
attr_accessor :rescue_os
|
15
|
+
attr_accessor :rescue_os_bit
|
16
|
+
attr_accessor :actions
|
17
|
+
attr_accessor :hostname
|
18
|
+
attr_accessor :post_install
|
19
|
+
attr_accessor :post_install_remote
|
20
|
+
attr_accessor :public_keys
|
21
|
+
attr_accessor :bootstrap_cmd
|
22
|
+
attr_accessor :logger
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
@rescue_os = 'freebsd'
|
26
|
+
@rescue_os_bit = '64'
|
27
|
+
@retries = 0
|
28
|
+
@login = 'root'
|
29
|
+
|
30
|
+
options.each_pair do |k,v|
|
31
|
+
self.send("#{k}=", v)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def enable_rescue_mode(options = {})
|
36
|
+
result = @api.enable_rescue! @ip, @rescue_os, @rescue_os_bit
|
37
|
+
if result.success? && result['rescue']
|
38
|
+
@password = result['rescue']['password']
|
39
|
+
reset_retries
|
40
|
+
logger.info "IP: #{ip} | username: #{@login} | password: #{@password}".colorize(:magenta)
|
41
|
+
elsif @retries > 3
|
42
|
+
logger.error "Rescue system could not be activated".colorize(:red)
|
43
|
+
raise CantActivateRescueSystemError, result
|
44
|
+
else
|
45
|
+
@retries += 1
|
46
|
+
|
47
|
+
logger.warn "Problem while trying to activate rescue system (retries: #{@retries})".colorize(:yellow)
|
48
|
+
@api.disable_rescue! @ip
|
49
|
+
|
50
|
+
rolling_sleep
|
51
|
+
enable_rescue_mode options
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset(options = {})
|
56
|
+
result = @api.reset! @ip, :hw
|
57
|
+
|
58
|
+
if result.success?
|
59
|
+
reset_retries
|
60
|
+
elsif @retries > 3
|
61
|
+
logger.error "Resetting through web service failed.".colorize(:red)
|
62
|
+
raise CantResetSystemError, result
|
63
|
+
else
|
64
|
+
@retries += 1
|
65
|
+
logger.warn "Problem while trying to reset/reboot system (retries: #{@retries})".colorize(:yellow)
|
66
|
+
rolling_sleep
|
67
|
+
reset options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def port_open? ip, port
|
72
|
+
ssh_port_probe = TCPSocket.new ip, port
|
73
|
+
IO.select([ssh_port_probe], nil, nil, 2)
|
74
|
+
ssh_port_probe.close
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def wait_for_ssh_down(options = {})
|
79
|
+
loop do
|
80
|
+
sleep 2
|
81
|
+
Timeout::timeout(4) do
|
82
|
+
if port_open? @ip, 22
|
83
|
+
logger.debug "SSH UP".colorize(:magenta)
|
84
|
+
else
|
85
|
+
raise Errno::ECONNREFUSED
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue Timeout::Error, Errno::ECONNREFUSED
|
90
|
+
logger.debug "SSH down".colorize(:magenta)
|
91
|
+
end
|
92
|
+
|
93
|
+
def wait_for_ssh_up(options = {})
|
94
|
+
loop do
|
95
|
+
Timeout::timeout(4) do
|
96
|
+
if port_open? @ip, 22
|
97
|
+
logger.debug "SSH up".colorize(:magenta)
|
98
|
+
return true
|
99
|
+
else
|
100
|
+
raise Errno::ECONNREFUSED
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
rescue Errno::ECONNREFUSED, Timeout::Error
|
105
|
+
logger.debug "SSH down".colorize(:magenta)
|
106
|
+
sleep 2
|
107
|
+
retry
|
108
|
+
end
|
109
|
+
|
110
|
+
def reboot(options = {})
|
111
|
+
logger.info "Rebooting ...".colorize(:magenta)
|
112
|
+
remote do |ssh|
|
113
|
+
ssh.exec!("reboot")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def verify_installation(options = {})
|
118
|
+
logger.info "Verifying the installation ...".colorize(:magenta)
|
119
|
+
#@login = 'root'
|
120
|
+
#remote(password: nil) do |ssh|
|
121
|
+
# working_hostname = ssh.exec!("cat /etc/hostname")
|
122
|
+
# if @hostname == working_hostname.chomp
|
123
|
+
# logger.info "The installation has been successful".colorize(:green)
|
124
|
+
# else
|
125
|
+
# raise InstallationError, "Hostnames do not match: assumed #{@hostname} but received #{working_hostname}"
|
126
|
+
# end
|
127
|
+
#end
|
128
|
+
end
|
129
|
+
|
130
|
+
def remove_from_local_known_hosts(options = {})
|
131
|
+
`ssh-keygen -R #{@hostname}`
|
132
|
+
`ssh-keygen -R #{@ip}`
|
133
|
+
end
|
134
|
+
|
135
|
+
def update_local_known_hosts(options = {})
|
136
|
+
remote do |ssh|
|
137
|
+
logger.info "Removing SSH keys for #{@hostname} from local ~/.ssh/known_hosts file ...".colorize(:magenta)
|
138
|
+
`ssh-keygen -R #{@hostname}`
|
139
|
+
`ssh-keygen -R #{@ip}`
|
140
|
+
end
|
141
|
+
rescue Net::SSH::HostKeyMismatch => e
|
142
|
+
e.remember_host!
|
143
|
+
logger.info "Remote host key has been added to local ~/.ssh/known_hosts file.".colorize(:green)
|
144
|
+
end
|
145
|
+
|
146
|
+
def use_api(api_obj)
|
147
|
+
@api = api_obj
|
148
|
+
end
|
149
|
+
|
150
|
+
def use_logger(logger_obj)
|
151
|
+
@logger = logger_obj
|
152
|
+
@logger.formatter = default_log_formatter
|
153
|
+
end
|
154
|
+
|
155
|
+
def remote(options = {}, &block)
|
156
|
+
default = { :password => @password }
|
157
|
+
default.merge! options
|
158
|
+
Net::SSH.start(@ip, @login, default) do |ssh|
|
159
|
+
block.call ssh
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def local(&block)
|
164
|
+
block.call
|
165
|
+
end
|
166
|
+
|
167
|
+
def reset_retries
|
168
|
+
@retries = 0
|
169
|
+
end
|
170
|
+
|
171
|
+
def rolling_sleep
|
172
|
+
sleep @retries * @retries * 3 + 1 # => 1, 4, 13, 28, 49, 76, 109, 148, 193, 244, 301, 364 ... seconds
|
173
|
+
end
|
174
|
+
|
175
|
+
def default_log_formatter
|
176
|
+
proc do |severity, datetime, progname, msg|
|
177
|
+
caller[4]=~/`(.*?)'/
|
178
|
+
"[#{datetime.strftime "%H:%M:%S"}][#{sprintf "%-15s", ip}][#{$1}] #{msg}\n"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class CantActivateRescueSystemError < StandardError; end
|
183
|
+
class CantResetSystemError < StandardError; end
|
184
|
+
class InstallationError < StandardError; end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'hetzner-api'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe "Bootstrap" do
|
5
|
+
before(:all) do
|
6
|
+
@api = Hetzner::API.new ENV['HETZNER_ROBOT_USERNAME'], ENV['HETZNER_ROBOT_PASSWORD']
|
7
|
+
@bootstrap = Hetzner::Boot::FreeBSD.new :api => @api
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".add_target(target)" do
|
11
|
+
it "should be able to add a server to operate on" do
|
12
|
+
@bootstrap.add_target dummy_target
|
13
|
+
expect(@bootstrap.targets.size).to equal(1)
|
14
|
+
expect(@bootstrap.targets.first).to be_an_instance_of(Hetzner::Boot::FreeBSD::Target)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def dummy_target
|
19
|
+
return {
|
20
|
+
:ip => "1.2.3.4",
|
21
|
+
:login => "MyHetznerUsername",
|
22
|
+
:password => "MyVerySecretPW",
|
23
|
+
:rescue_os => "freebsd",
|
24
|
+
:rescue_os_bit => "64"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hetzner-boot-freebsd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Pilka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hetzner-api
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.9.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.9.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: erubis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.7.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: colorize
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.7.5
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.7.5
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: highline
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.7.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.7.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.0.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.0.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.5'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec-nc
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: guard
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: guard-rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: pry
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: pry-remote
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: pry-nav
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: coveralls
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: crimp
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :runtime
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
description: Allows to reboot a physical server hosted at Hetzner into a minimal FreeBSD
|
252
|
+
recscue environment taht is based on mfsBSD.
|
253
|
+
email:
|
254
|
+
- c.pilka@asconix.com
|
255
|
+
executables: []
|
256
|
+
extensions: []
|
257
|
+
extra_rdoc_files: []
|
258
|
+
files:
|
259
|
+
- ".gitignore"
|
260
|
+
- ".ruby-gemset"
|
261
|
+
- ".ruby-version"
|
262
|
+
- Gemfile
|
263
|
+
- LICENSE
|
264
|
+
- README.md
|
265
|
+
- Rakefile
|
266
|
+
- example.rb
|
267
|
+
- hetzner-boot-freebsd.gemspec
|
268
|
+
- lib/hetzner-boot-freebsd.rb
|
269
|
+
- lib/hetzner/boot/freebsd/target.rb
|
270
|
+
- lib/hetzner/boot/freebsd/version.rb
|
271
|
+
- spec/hetzner-boot-freebsd_spec.rb
|
272
|
+
- spec/spec_helper.rb
|
273
|
+
homepage: http://www.asconix.com
|
274
|
+
licenses:
|
275
|
+
- BSD-3
|
276
|
+
metadata: {}
|
277
|
+
post_install_message:
|
278
|
+
rdoc_options: []
|
279
|
+
require_paths:
|
280
|
+
- lib
|
281
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
282
|
+
requirements:
|
283
|
+
- - ">="
|
284
|
+
- !ruby/object:Gem::Version
|
285
|
+
version: '0'
|
286
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
287
|
+
requirements:
|
288
|
+
- - ">="
|
289
|
+
- !ruby/object:Gem::Version
|
290
|
+
version: '0'
|
291
|
+
requirements: []
|
292
|
+
rubyforge_project:
|
293
|
+
rubygems_version: 2.4.5
|
294
|
+
signing_key:
|
295
|
+
specification_version: 4
|
296
|
+
summary: Reboot a Hetzner server into FreeBSD rescue environment
|
297
|
+
test_files:
|
298
|
+
- spec/hetzner-boot-freebsd_spec.rb
|
299
|
+
- spec/spec_helper.rb
|