teleport 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +62 -0
- data/Rakefile +10 -0
- data/bin/teleport +7 -0
- data/lib/teleport.rb +7 -0
- data/lib/teleport/config.rb +129 -0
- data/lib/teleport/constants.rb +19 -0
- data/lib/teleport/install.rb +227 -0
- data/lib/teleport/main.rb +110 -0
- data/lib/teleport/mirror.rb +103 -0
- data/lib/teleport/run.sh +123 -0
- data/lib/teleport/util.rb +319 -0
- data/lib/teleport/version.rb +4 -0
- data/teleport.gemspec +20 -0
- metadata +83 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Adam Doppelt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
## Welcome to Teleport
|
2
|
+
|
3
|
+
Teleport is a lightweight way to set up Ubuntu machines. The name derives from the mechanism that teleport uses to setup the target machine - it copies itself onto the target machine via ssh and then runs itself there. In effect, it "teleports" to the target. This design makes it possible for teleport to bootstrap itself onto a fresh machine. There's no need to install ruby or anything else by hand.
|
4
|
+
|
5
|
+
Teleport strives to be **idempotent** - you can run it repeatedly without changing the result. In other words, as you build up your teleport config file you can generally run it over and over again without fear of breaking the target machine.
|
6
|
+
|
7
|
+
Teleport is great for managing a small number of hosted machines, either dedicated or in the cloud. Due to it's opinionated nature and limited scope you may find that it works better for you than other, more complicated tools.
|
8
|
+
|
9
|
+
At the moment Teleport supports **Ubuntu 10.04/10.10/11.04 with Ruby 1.8.7, 1.9.2, or [REE](http://www.rubyenterpriseedition.com/)**.
|
10
|
+
|
11
|
+
## Getting Started
|
12
|
+
|
13
|
+
1. Install Teleport on your local machine.
|
14
|
+
|
15
|
+
```
|
16
|
+
$ sudo gem install teleport --pre
|
17
|
+
```
|
18
|
+
|
19
|
+
1. Create a `Telfile` config file. Here's a simple example. Note that we actually define two machines, `server_app1` and `server_db1`:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ mkdir ~/teleport
|
23
|
+
$ cd ~/teleport
|
24
|
+
```
|
25
|
+
|
26
|
+
Put this into `~/teleport/Telfile`:
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
user "admin"
|
30
|
+
ruby "1.9.2"
|
31
|
+
apt "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen", :key => "7F0CAB10"
|
32
|
+
role :app, :packages => [:memcached]
|
33
|
+
role :db, :packages => [:mongodb-10gen]
|
34
|
+
server "server_app1", :role => :app
|
35
|
+
server "server_db1", :role => :db
|
36
|
+
packages [:atop, :emacs, :gcc]
|
37
|
+
```
|
38
|
+
|
39
|
+
1. You'll want to copy files to your new machines too. Put the files into your teleport directory. For example, maybe you want to automatically have your `.bashrc` and `.emacs` files copied to your new server. You'll want the `memcached` and `mongodb` config files too. Here's what your teleport directory should look like:
|
40
|
+
|
41
|
+
```
|
42
|
+
Telfile
|
43
|
+
files/home/admin/.bashrc
|
44
|
+
files/home/admin/.emacs
|
45
|
+
files_app/etc/default/memcached
|
46
|
+
files_app/etc/memcached.conf
|
47
|
+
files_db/etc/mongodb.conf
|
48
|
+
```
|
49
|
+
|
50
|
+
1. Now run Teleport:
|
51
|
+
|
52
|
+
```
|
53
|
+
$ teleport server_app1
|
54
|
+
```
|
55
|
+
|
56
|
+
Teleport will ssh to the machine and set it up per your instructions.
|
57
|
+
|
58
|
+
## Full Documentation
|
59
|
+
|
60
|
+
Full docs are in the wiki:
|
61
|
+
|
62
|
+
https://github.com/rglabs/teleport/wiki
|
data/Rakefile
ADDED
data/bin/teleport
ADDED
data/lib/teleport.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
module Teleport
|
2
|
+
# This class parses Telfile, and includes DSL and the models.
|
3
|
+
class Config
|
4
|
+
RUBIES = ["1.9.2", "REE", "1.8.7"]
|
5
|
+
PATH = "Telfile"
|
6
|
+
|
7
|
+
attr_accessor :user, :ruby, :roles, :servers, :apt, :packages, :callbacks, :dsl
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@roles = []
|
11
|
+
@servers = []
|
12
|
+
@apt = []
|
13
|
+
@packages = []
|
14
|
+
@callbacks = { }
|
15
|
+
|
16
|
+
@dsl = DSL.new(self)
|
17
|
+
@dsl.instance_eval(File.read(PATH), PATH)
|
18
|
+
|
19
|
+
@user ||= Util.whoami
|
20
|
+
@ruby ||= RUBIES.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def role(n)
|
24
|
+
@roles.find { |i| i.name == n.to_sym }
|
25
|
+
end
|
26
|
+
|
27
|
+
def server(n)
|
28
|
+
@servers.find { |i| i.name == n.to_s }
|
29
|
+
end
|
30
|
+
|
31
|
+
# The model for role in the Telfile.
|
32
|
+
class Role
|
33
|
+
attr_reader :name, :options, :packages
|
34
|
+
|
35
|
+
def initialize(name, options)
|
36
|
+
raise "role name must be a sym" if !name.is_a?(Symbol)
|
37
|
+
raise "role options must be a hash" if !options.is_a?(Hash)
|
38
|
+
|
39
|
+
@name, @options, @packages = name, options, []
|
40
|
+
if p = @options.delete(:packages)
|
41
|
+
raise "role :packages must be an array" if !p.is_a?(Array)
|
42
|
+
@packages = p
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# The model for server in the Telfile.
|
48
|
+
class Server
|
49
|
+
attr_reader :name, :options, :packages
|
50
|
+
|
51
|
+
def initialize(name, options)
|
52
|
+
raise "server name must be a string" if !name.is_a?(String)
|
53
|
+
raise "server options must be a hash" if !options.is_a?(Hash)
|
54
|
+
raise "server :role must be a sym" if !options[:role].is_a?(Symbol)
|
55
|
+
|
56
|
+
@name, @options, @packages = name, options, []
|
57
|
+
if p = @options.delete(:packages)
|
58
|
+
raise "server :packages must be an array" if !p.is_a?(Array)
|
59
|
+
@packages = p
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# The model for an apt line in the Telfile.
|
65
|
+
class Apt
|
66
|
+
attr_reader :line, :options
|
67
|
+
|
68
|
+
def initialize(line, options)
|
69
|
+
raise "apt line must be a string" if !line.is_a?(String)
|
70
|
+
raise "apt options must be a hash" if !options.is_a?(Hash)
|
71
|
+
@line, @options = line, options
|
72
|
+
|
73
|
+
if k = @options[:key]
|
74
|
+
raise "apt :key must be an String" if !k.is_a?(String)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# DSL used when parsing Telfile.
|
80
|
+
class DSL
|
81
|
+
def initialize(config)
|
82
|
+
@config = config
|
83
|
+
end
|
84
|
+
|
85
|
+
def ruby(v)
|
86
|
+
raise "ruby called twice" if @config.ruby
|
87
|
+
raise "ruby must be a string" if !v.is_a?(String)
|
88
|
+
raise "don't recognize ruby #{v.inspect}." if !Config::RUBIES.include?(v)
|
89
|
+
@config.ruby = v
|
90
|
+
end
|
91
|
+
|
92
|
+
def user(v)
|
93
|
+
raise "user called twice" if @config.user
|
94
|
+
raise "user must be a string" if !v.is_a?(String)
|
95
|
+
@config.user = v
|
96
|
+
end
|
97
|
+
|
98
|
+
def role(name, options = {})
|
99
|
+
raise "role #{name.inspect} defined twice" if @config.roles.any? { |i| i.name == name }
|
100
|
+
@config.roles << Role.new(name, options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def server(name, options = {})
|
104
|
+
raise "server #{name.inspect} defined twice" if @config.servers.any? { |i| i.name == name }
|
105
|
+
@config.servers << Server.new(name, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def apt(line, options = {})
|
109
|
+
@config.apt << Apt.new(line, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def packages(*list)
|
113
|
+
@config.packages += list.flatten
|
114
|
+
end
|
115
|
+
|
116
|
+
%w(install user packages files).each do |op|
|
117
|
+
%w(before after).each do |before_after|
|
118
|
+
callback = "#{before_after}_#{op}".to_sym
|
119
|
+
define_method(callback) do |&block|
|
120
|
+
if @config.callbacks[callback]
|
121
|
+
raise "you already defined the #{callback} callback"
|
122
|
+
end
|
123
|
+
@config.callbacks[callback] = block
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Constants
|
2
|
+
# The temp directory that teleport uses on the target machine.
|
3
|
+
DIR = "/tmp/_teleported"
|
4
|
+
|
5
|
+
# The teleport gem within our temp directory.
|
6
|
+
GEM = "#{DIR}/gem"
|
7
|
+
|
8
|
+
# Directory where user install data (Telfile and friends) is stored.
|
9
|
+
DATA = "#{DIR}/data"
|
10
|
+
|
11
|
+
# Directory where user install files are stored.
|
12
|
+
FILES = "#{DIR}/data/files"
|
13
|
+
|
14
|
+
# Name of the public key to install on the target machine.
|
15
|
+
PUBKEY = "id_teleport.pub"
|
16
|
+
|
17
|
+
# Minimum version of rubygems to install.
|
18
|
+
RUBYGEMS = "1.8.5"
|
19
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
module Teleport
|
2
|
+
# Class that performs the install on the target machine.
|
3
|
+
class Install
|
4
|
+
include Constants
|
5
|
+
include Util
|
6
|
+
include Mirror
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
run_verbose!
|
11
|
+
_read_config
|
12
|
+
|
13
|
+
# setup @config constants
|
14
|
+
Config::DSL.const_set("HOST", @host)
|
15
|
+
Config::DSL.const_set("USER", @config.user)
|
16
|
+
Config::DSL.const_set("ROLE", @role && @role.name)
|
17
|
+
|
18
|
+
# add mixins
|
19
|
+
@config.dsl.extend(Mirror)
|
20
|
+
@config.dsl.extend(Util)
|
21
|
+
@config.dsl.run_verbose!
|
22
|
+
|
23
|
+
_with_callback(:install) do
|
24
|
+
_gems
|
25
|
+
_hostname
|
26
|
+
_with_callback(:user) do
|
27
|
+
_create_user
|
28
|
+
end
|
29
|
+
_apt
|
30
|
+
_with_callback(:packages) do
|
31
|
+
_packages
|
32
|
+
end
|
33
|
+
_with_callback(:files) do
|
34
|
+
_files
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def _read_config
|
42
|
+
# read DIR/config to get CONFIG_HOST (and set @host)
|
43
|
+
config_file = { }
|
44
|
+
File.readlines("config").each do |i|
|
45
|
+
if i =~ /CONFIG_([^=]+)='([^']*)'/
|
46
|
+
config_file[$1.downcase.to_sym] = $2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@host = config_file[:host]
|
50
|
+
|
51
|
+
# do we have a server object?
|
52
|
+
@server = @config.server(@host)
|
53
|
+
if !@server && !@config.servers.empty?
|
54
|
+
fatal "Hm. I couldn't find server #{@host.inspect} in teleport.rb."
|
55
|
+
end
|
56
|
+
|
57
|
+
@role = nil
|
58
|
+
if @server && (role_name = @server.options[:role])
|
59
|
+
@role = @config.role(role_name)
|
60
|
+
if !@role
|
61
|
+
fatal "Hm. I couldn't find role #{role_name.inspect} in teleport.rb."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def _gems
|
67
|
+
banner "Gems..."
|
68
|
+
|
69
|
+
# update rubygems if necessary
|
70
|
+
gem_version = `gem --version`.strip.split(".").map(&:to_i)
|
71
|
+
if (gem_version <=> RUBYGEMS.split(".").map(&:to_i)) == -1
|
72
|
+
banner "Upgrading rubygems..."
|
73
|
+
run "gem update --system"
|
74
|
+
end
|
75
|
+
|
76
|
+
# uninstall all gems except for bundler
|
77
|
+
gems = `gem list`.split("\n").map { |i| i.split.first }
|
78
|
+
gems.delete("bundler")
|
79
|
+
if !gems.empty?
|
80
|
+
banner "Uninstalling #{gems.length} system gems..."
|
81
|
+
gems.each do |i|
|
82
|
+
run "gem uninstall -aIx #{i}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# install bundler
|
87
|
+
gem_if_necessary("bundler")
|
88
|
+
end
|
89
|
+
|
90
|
+
def _hostname
|
91
|
+
banner "Hostname..."
|
92
|
+
old_hostname = `hostname`.strip
|
93
|
+
return if old_hostname == @host
|
94
|
+
|
95
|
+
puts "setting hostname to #{@host} (it was #{old_hostname})..."
|
96
|
+
File.open("/etc/hostname", "w") do |f|
|
97
|
+
f.write @host
|
98
|
+
end
|
99
|
+
run "hostname -F /etc/hostname"
|
100
|
+
|
101
|
+
puts "adding #{@host} to /etc/hosts ..."
|
102
|
+
_rewrite("/etc/hosts") do |fout|
|
103
|
+
hosts = File.read("/etc/hosts")
|
104
|
+
|
105
|
+
# old_hostname => @host
|
106
|
+
hosts.gsub!(_etc_hosts_regex(old_hostname), "\\1#{@host}\\2")
|
107
|
+
if hosts !~ _etc_hosts_regex(@host)
|
108
|
+
# not found? append to localhost
|
109
|
+
hosts.gsub!(_etc_hosts_regex("localhost"), "\\1localhost #{@host}\\2")
|
110
|
+
if hosts !~ _etc_hosts_regex(@host)
|
111
|
+
puts " Hm. I couldn't add it, unfortunately. You'll have to do it manually."
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
fout.write hosts
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def _create_user
|
120
|
+
user = @config.user
|
121
|
+
|
122
|
+
banner "Creating #{user} account..."
|
123
|
+
# create the account
|
124
|
+
if !File.directory?("/home/#{user}")
|
125
|
+
run "useradd --create-home --shell /bin/bash --groups adm #{user}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# try to sudo - if it fails, add user to sudoers
|
129
|
+
if fails?("sudo -u #{user} sudo -n echo gub")
|
130
|
+
puts "setting up sudoers..."
|
131
|
+
File.open("/etc/sudoers", "a") do |f|
|
132
|
+
f.puts <<EOF
|
133
|
+
# added by teleport
|
134
|
+
#{user} ALL=(ALL) NOPASSWD: ALL
|
135
|
+
EOF
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# ssh key, if present
|
140
|
+
if File.exists?(PUBKEY)
|
141
|
+
authorized_keys = "/home/#{user}/.ssh/authorized_keys"
|
142
|
+
if !File.exists?(authorized_keys)
|
143
|
+
puts "adding #{PUBKEY} to authorized_keys..."
|
144
|
+
mkdir_if_necessary(File.dirname(authorized_keys), user, 0700)
|
145
|
+
cp(PUBKEY, authorized_keys, user, 0600)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def _apt
|
151
|
+
return if @config.apt.empty?
|
152
|
+
banner "Apt..."
|
153
|
+
|
154
|
+
dirty = false
|
155
|
+
|
156
|
+
# keys
|
157
|
+
keys = @config.apt.map { |i| i.options[:key] }.compact
|
158
|
+
keys.each do |i|
|
159
|
+
if fails?("apt-key list | grep #{i}")
|
160
|
+
run "apt-key adv --keyserver keyserver.ubuntu.com --recv #{i}"
|
161
|
+
dirty = true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# teleport.list
|
166
|
+
apt = @config.apt.sort_by { |i| i.line }
|
167
|
+
rewrite = _rewrite("/etc/apt/sources.list.d/teleport.list") do |f|
|
168
|
+
f.puts "# Generated by teleport"
|
169
|
+
apt.each do |i|
|
170
|
+
f.puts i.line
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if dirty || rewrite
|
175
|
+
run "apt-get update"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def _packages
|
180
|
+
banner "Packages..."
|
181
|
+
list = @config.packages
|
182
|
+
list += @role.packages if @role
|
183
|
+
list += @server.packages if @server
|
184
|
+
list.sort.each { |i| package_if_necessary(i) }
|
185
|
+
end
|
186
|
+
|
187
|
+
def _files
|
188
|
+
banner "Files..."
|
189
|
+
files = ["files"]
|
190
|
+
files << "files_#{@role.name}" if @role
|
191
|
+
files.each do |i|
|
192
|
+
install_dir(i) if File.exists?("#{DATA}/#{i}")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
protected
|
197
|
+
|
198
|
+
def _with_callback(op, &block)
|
199
|
+
if before = @config.callbacks["before_#{op}".to_sym]
|
200
|
+
before.call
|
201
|
+
end
|
202
|
+
yield
|
203
|
+
if after = @config.callbacks["after_#{op}".to_sym]
|
204
|
+
after.call
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def _rewrite(path, &block)
|
209
|
+
tmp = "#{path}.tmp"
|
210
|
+
begin
|
211
|
+
File.open(tmp, "w") { |f| yield(f) }
|
212
|
+
if !File.exists?(path) || different?(path, tmp)
|
213
|
+
copy_perms(path, tmp) if File.exists?(path)
|
214
|
+
mv(tmp, path)
|
215
|
+
return true
|
216
|
+
end
|
217
|
+
ensure
|
218
|
+
File.unlink(tmp) if File.exists?(tmp)
|
219
|
+
end
|
220
|
+
false
|
221
|
+
end
|
222
|
+
|
223
|
+
def _etc_hosts_regex(host)
|
224
|
+
/^([^#]+[ \t])#{Regexp.escape(host)}([ \t]|$)/
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "getoptlong"
|
3
|
+
|
4
|
+
module Teleport
|
5
|
+
# The main class for the teleport command line.
|
6
|
+
class Main
|
7
|
+
include Constants
|
8
|
+
include Util
|
9
|
+
|
10
|
+
TAR = "#{DIR}.tgz"
|
11
|
+
|
12
|
+
attr_accessor :host, :options
|
13
|
+
|
14
|
+
def initialize(cmd = :teleport)
|
15
|
+
opts = GetoptLong.new(
|
16
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT]
|
17
|
+
)
|
18
|
+
opts.each do |opt, arg|
|
19
|
+
case opt
|
20
|
+
when "--help"
|
21
|
+
usage(0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
$stderr = $stdout
|
26
|
+
|
27
|
+
case cmd
|
28
|
+
when :teleport
|
29
|
+
teleport(ARGV.shift)
|
30
|
+
when :install
|
31
|
+
install
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def usage(exit_code)
|
36
|
+
puts "Usage: teleport <hostname>"
|
37
|
+
puts " --help print this help text"
|
38
|
+
exit(exit_code)
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_config
|
42
|
+
if !File.exists?(Config::PATH)
|
43
|
+
fatal("Sadly, I can't find #{Config::PATH} here. Please create one.")
|
44
|
+
end
|
45
|
+
@config = Config.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def assemble_tgz(host)
|
49
|
+
banner "Assembling #{TAR}..."
|
50
|
+
rm_and_mkdir(DIR)
|
51
|
+
|
52
|
+
# gem
|
53
|
+
run("cp", ["-r", "#{File.dirname(__FILE__)}/../../lib", GEM])
|
54
|
+
# data
|
55
|
+
run("cp", ["-r", ".", DATA])
|
56
|
+
# config.sh
|
57
|
+
File.open("#{DIR}/config", "w") do |f|
|
58
|
+
f.puts("CONFIG_HOST='#{host}'")
|
59
|
+
f.puts("CONFIG_RUBY='#{@config.ruby}'")
|
60
|
+
f.puts("CONFIG_RUBYGEMS='#{RUBYGEMS}'")
|
61
|
+
end
|
62
|
+
# keys
|
63
|
+
ssh_key = "#{ENV["HOME"]}/.ssh/#{PUBKEY}"
|
64
|
+
if File.exists?(ssh_key)
|
65
|
+
run("cp", [ssh_key, DIR])
|
66
|
+
else
|
67
|
+
puts "Could not find #{ssh_key} - skipping."
|
68
|
+
end
|
69
|
+
|
70
|
+
Dir.chdir(File.dirname(DIR)) do
|
71
|
+
run("tar", ["cfpz", TAR, File.basename(DIR)])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def ssh_tgz(host)
|
76
|
+
begin
|
77
|
+
banner "scp #{TAR} to #{host}:#{TAR}..."
|
78
|
+
run "scp #{TAR} #{host}:#{TAR}"
|
79
|
+
|
80
|
+
cmd = [
|
81
|
+
"cd /tmp",
|
82
|
+
"(sudo -n echo gub > /dev/null 2> /dev/null || (echo `whoami` could not sudo. && exit 1))",
|
83
|
+
"sudo rm -rf #{DIR}",
|
84
|
+
"sudo tar xfpz #{TAR}",
|
85
|
+
"sudo #{DIR}/gem/teleport/run.sh"
|
86
|
+
]
|
87
|
+
banner "ssh to #{host} and run..."
|
88
|
+
run("ssh", [host, cmd.join(" && ")])
|
89
|
+
rescue RunError
|
90
|
+
fatal("Failed!")
|
91
|
+
end
|
92
|
+
banner "Success!"
|
93
|
+
end
|
94
|
+
|
95
|
+
def teleport(host)
|
96
|
+
read_config
|
97
|
+
usage(1) if !host
|
98
|
+
assemble_tgz(host)
|
99
|
+
ssh_tgz(host)
|
100
|
+
end
|
101
|
+
|
102
|
+
def install
|
103
|
+
Dir.chdir(DATA) do
|
104
|
+
read_config
|
105
|
+
end
|
106
|
+
Install.new(@config)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "erb"
|
2
|
+
|
3
|
+
module Teleport
|
4
|
+
# Helper module for recursively mirroring files from a src to a dst.
|
5
|
+
module Mirror
|
6
|
+
include Constants
|
7
|
+
|
8
|
+
# Install file from the teleport data directory into the normal
|
9
|
+
# filesystem. Path can use a few different formats:
|
10
|
+
#
|
11
|
+
# * #{DATA}/xyz - Full path into the data directory
|
12
|
+
# * files/xyz - Path into the #{DATA}/files directory
|
13
|
+
# * files_role/xyz - Path into a role directory
|
14
|
+
# * xyz - Assumed to be #{DATA}/files/xyz
|
15
|
+
#
|
16
|
+
# Note that the path can be an erb file. For example,
|
17
|
+
# "etc/hosts.erb" will be installed as "etc/hosts".
|
18
|
+
#
|
19
|
+
# Returns true if the file was installed, false if the file had
|
20
|
+
# previously been installed and no change was required.
|
21
|
+
def install_file(path)
|
22
|
+
path, dst = path_to_src(path), path_to_dst(path)
|
23
|
+
|
24
|
+
# run erb if necessary
|
25
|
+
if path =~ /#{DATA}\/(.*)\.erb$/
|
26
|
+
tmp = "#{DATA}/#{$1.gsub('/', '_')}"
|
27
|
+
dst = dst.gsub(/\.erb$/, "")
|
28
|
+
File.open(tmp, "w") do |f|
|
29
|
+
f.write ERB.new(File.read(path)).result(binding)
|
30
|
+
end
|
31
|
+
copy_metadata(path, tmp)
|
32
|
+
path = tmp
|
33
|
+
end
|
34
|
+
|
35
|
+
cp_if_necessary(path, dst, user_for_file(dst), mode_for_file(dst))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Install directory from the teleport data directory into the
|
39
|
+
# normal filesystem. Path can use a few different formats:
|
40
|
+
#
|
41
|
+
# * #{DATA}/xyz - Full path into the data directory
|
42
|
+
# * files/xyz - Path into the #{DATA}/files directory
|
43
|
+
# * files_role/xyz - Path into a role directory
|
44
|
+
# * xyz - Assumed to be #{DATA}/files/xyz
|
45
|
+
#
|
46
|
+
# Returns true if the dir was installed, false if the dir had
|
47
|
+
# previously been installed and no change was required.
|
48
|
+
def install_dir(path)
|
49
|
+
dirty = false
|
50
|
+
|
51
|
+
path, dst = path_to_src(path), path_to_dst(path)
|
52
|
+
mkdir_if_necessary(dst, user_for_file(dst)) if !dst.empty?
|
53
|
+
|
54
|
+
files = Dir.new(path).to_a.sort
|
55
|
+
files.delete_if { |i| i == "." || i == ".." || i =~ /^.#/ }
|
56
|
+
files.each do |i|
|
57
|
+
i = "#{path}/#{i}"
|
58
|
+
if File.directory?(i)
|
59
|
+
dirty = install_dir(i) || dirty
|
60
|
+
else
|
61
|
+
dirty = install_file(i) || dirty
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
dirty
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def normalize_path(path)
|
71
|
+
case path
|
72
|
+
when /^#{DATA}/
|
73
|
+
# already absolute - do nothing
|
74
|
+
when /^files/
|
75
|
+
path = "#{DATA}/#{path}"
|
76
|
+
else
|
77
|
+
path = "#{DATA}/files/#{path}"
|
78
|
+
end
|
79
|
+
path
|
80
|
+
end
|
81
|
+
|
82
|
+
def path_to_src(path)
|
83
|
+
normalize_path(path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def path_to_dst(path)
|
87
|
+
path = normalize_path(path)
|
88
|
+
path = path[%r{#{DATA}/files[^/]*(.*)}, 1]
|
89
|
+
path
|
90
|
+
end
|
91
|
+
|
92
|
+
def user_for_file(f)
|
93
|
+
f[%r{^/home/([^/]+)}, 1] || "root"
|
94
|
+
end
|
95
|
+
|
96
|
+
def mode_for_file(f)
|
97
|
+
case f
|
98
|
+
when %r{sudoers} then 0440
|
99
|
+
when %r{/\.ssh/} then 0400
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/teleport/run.sh
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
#
|
4
|
+
# This script runs on the target machine. First it installs ruby if
|
5
|
+
# necessary, then it runs teleport.
|
6
|
+
#
|
7
|
+
|
8
|
+
# bail on errors
|
9
|
+
set -eu
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
#
|
14
|
+
# functions
|
15
|
+
#
|
16
|
+
|
17
|
+
function banner() {
|
18
|
+
printf '\e[1;37;43m[%s] %-72s\e[0m\n' `date '+%H:%M:%S'` "run.sh: $1"
|
19
|
+
}
|
20
|
+
|
21
|
+
function fatal() {
|
22
|
+
printf '\e[1;37;41m[%s] %-72s\e[0m\n' `date '+%H:%M:%S'` "run.sh: error - $1"
|
23
|
+
exit 1
|
24
|
+
}
|
25
|
+
|
26
|
+
function install_ruby() {
|
27
|
+
banner "apt-get update / upgrade..."
|
28
|
+
sudo apt-get update
|
29
|
+
sudo apt-get -y upgrade
|
30
|
+
sudo apt-get install -y wget libreadline5-dev
|
31
|
+
|
32
|
+
banner "installing Ruby $CONFIG_RUBY..."
|
33
|
+
case $CONFIG_RUBY in
|
34
|
+
1.8.7 ) install_ruby_187 ;;
|
35
|
+
1.9.2 ) install_ruby_192 ;;
|
36
|
+
REE ) install_ruby_ree ;;
|
37
|
+
* ) fatal "unknown ruby ($CONFIG_RUBY)" ;;
|
38
|
+
esac
|
39
|
+
}
|
40
|
+
|
41
|
+
function install_ruby_187() {
|
42
|
+
sudo apt-get -y install irb libopenssl-ruby libreadline-ruby rdoc ri ruby ruby-dev
|
43
|
+
|
44
|
+
wget http://production.cf.rubygems.org/rubygems/rubygems-$CONFIG_RUBYGEMS.tgz
|
45
|
+
tar xfpz rubygems-$CONFIG_RUBYGEMS.tgz
|
46
|
+
(cd rubygems-$CONFIG_RUBYGEMS ; ruby setup.rb)
|
47
|
+
ln -s /usr/bin/gem1.8 /usr/bin/gem
|
48
|
+
}
|
49
|
+
|
50
|
+
function install_ruby_192() {
|
51
|
+
local patch=p180
|
52
|
+
|
53
|
+
# see http://threebrothers.org/brendan/blog/ruby-1-9-2-on-ubuntu-11-04/
|
54
|
+
sudo apt-get install -y bison build-essential checkinstall libffi5 libssl-dev libyaml-dev zlib1g-dev
|
55
|
+
|
56
|
+
wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-$patch.tar.gz
|
57
|
+
tar xvzf ruby-1.9.2-$patch.tar.gz
|
58
|
+
|
59
|
+
cd ruby-1.9.2-$patch
|
60
|
+
./configure --prefix=/usr/local \
|
61
|
+
--program-suffix=1.9.2 \
|
62
|
+
--with-ruby-version=1.9.2 \
|
63
|
+
--disable-install-doc
|
64
|
+
make
|
65
|
+
sudo checkinstall -D -y \
|
66
|
+
--fstrans=no \
|
67
|
+
--nodoc \
|
68
|
+
--pkgname="ruby1.9.2" \
|
69
|
+
--pkgversion="1.9.2-$patch" \
|
70
|
+
--provides="ruby"
|
71
|
+
cd ..
|
72
|
+
|
73
|
+
sudo update-alternatives --install /usr/local/bin/ruby ruby /usr/local/bin/ruby1.9.2 500 \
|
74
|
+
--slave /usr/local/bin/ri ri /usr/local/bin/ri1.9.2 \
|
75
|
+
--slave /usr/local/bin/irb irb /usr/local/bin/irb1.9.2 \
|
76
|
+
--slave /usr/local/bin/gem gem /usr/local/bin/gem1.9.2 \
|
77
|
+
--slave /usr/local/bin/erb erb /usr/local/bin/erb1.9.2 \
|
78
|
+
--slave /usr/local/bin/rdoc rdoc /usr/local/bin/rdoc1.9.2
|
79
|
+
}
|
80
|
+
|
81
|
+
function install_ruby_ree() {
|
82
|
+
local ree="ruby-enterprise_1.8.7-2011.03_${ARCH}_ubuntu10.04.deb"
|
83
|
+
wget http://rubyenterpriseedition.googlecode.com/files/$ree
|
84
|
+
sudo dpkg -i $ree
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
#
|
89
|
+
# main
|
90
|
+
#
|
91
|
+
|
92
|
+
# are we on Ubuntu?
|
93
|
+
if ! uname -a | grep -q Ubuntu ; then
|
94
|
+
fatal "Teleport only works with Ubuntu"
|
95
|
+
fi
|
96
|
+
|
97
|
+
# which version?
|
98
|
+
. /etc/lsb-release
|
99
|
+
case $($DISTRIB_RELEASE) in
|
100
|
+
10.* ) ;; # nop
|
101
|
+
11.04) ;; # nop
|
102
|
+
*)
|
103
|
+
banner "warning - Ubuntu $DISTRIB_RELEASE hasn't been tested with Teleport yet"
|
104
|
+
esac
|
105
|
+
|
106
|
+
# which architecture?
|
107
|
+
if [ $(uname -m) == "x86_64" ] ; then
|
108
|
+
ARCH=amd64
|
109
|
+
else
|
110
|
+
ARCH=i386
|
111
|
+
fi
|
112
|
+
|
113
|
+
# read our config
|
114
|
+
cd /tmp/_teleported
|
115
|
+
source ./config
|
116
|
+
|
117
|
+
# do we need to install ruby?
|
118
|
+
if ! which ruby > /dev/null ; then
|
119
|
+
install_ruby
|
120
|
+
fi
|
121
|
+
|
122
|
+
# run teleport!
|
123
|
+
ruby -I gem -r teleport -e "Teleport::Main.new(:install)"
|
@@ -0,0 +1,319 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "etc"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Teleport
|
6
|
+
# Helper module for executing commands and printing stuff
|
7
|
+
# out.
|
8
|
+
#
|
9
|
+
# The general idea is to only print commands that are actually
|
10
|
+
# interesting. For example, mkdir_if_necessary won't print anything
|
11
|
+
# if the directory already exists. That way we can scan teleport
|
12
|
+
# output and see what changes were made without getting lost in
|
13
|
+
# repetitive commands that had no actual effect.
|
14
|
+
module Util
|
15
|
+
class RunError < StandardError ; end
|
16
|
+
|
17
|
+
extend self
|
18
|
+
|
19
|
+
RESET = "\e[0m"
|
20
|
+
RED = "\e[1;37;41m"
|
21
|
+
GREEN = "\e[1;37;42m"
|
22
|
+
YELLOW = "\e[1;37;43m"
|
23
|
+
BLUE = "\e[1;37;44m"
|
24
|
+
MAGENTA = "\e[1;37;45m"
|
25
|
+
CYAN = "\e[1;37;46m"
|
26
|
+
|
27
|
+
#
|
28
|
+
# running commands
|
29
|
+
#
|
30
|
+
|
31
|
+
# Make all commands echo before running.
|
32
|
+
def run_verbose!
|
33
|
+
@run_verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Run a command, raise an error upon failure. Output goes to
|
37
|
+
# $stdout/$stderr.
|
38
|
+
def run(command, args = nil)
|
39
|
+
line = nil
|
40
|
+
if args
|
41
|
+
args = args.map(&:to_s)
|
42
|
+
line = "#{command} #{args.join(" ")}"
|
43
|
+
vputs line
|
44
|
+
system(command, *args)
|
45
|
+
else
|
46
|
+
line = command
|
47
|
+
vputs line
|
48
|
+
system(command)
|
49
|
+
end
|
50
|
+
if $? != 0
|
51
|
+
if $?.termsig == Signal.list["INT"]
|
52
|
+
raise "#{line} interrupted"
|
53
|
+
end
|
54
|
+
raise RunError, "#{line} failed : #{$?.to_i / 256}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Run a command, raise an error upon failure. The output is
|
59
|
+
# capture as a string and returned.
|
60
|
+
def run_capture(command, *args)
|
61
|
+
if !args.empty?
|
62
|
+
args = args.flatten.map { |i| shell_escape(i) }.join(" ")
|
63
|
+
command = "#{command} #{args}"
|
64
|
+
end
|
65
|
+
result = `#{command}`
|
66
|
+
if $? != 0
|
67
|
+
if $?.termsig == Signal.list["INT"]
|
68
|
+
raise "#{command} interrupted"
|
69
|
+
end
|
70
|
+
raise RunError, "#{command} failed : #{$?.to_i / 256} #{result.inspect}"
|
71
|
+
end
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
# Run a command but don't send any output to $stdout/$stderr.
|
76
|
+
def run_quietly(command, *args)
|
77
|
+
if !args.empty?
|
78
|
+
args = args.flatten.map { |i| shell_escape(i) }.join(" ")
|
79
|
+
command = "#{command} #{args}"
|
80
|
+
end
|
81
|
+
run("#{command} > /dev/null 2> /dev/null")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Run a command, return true if it succeeds.
|
85
|
+
def succeeds?(command)
|
86
|
+
system("#{command} > /dev/null 2> /dev/null")
|
87
|
+
$? == 0
|
88
|
+
end
|
89
|
+
|
90
|
+
# Run a command, return true if it fails.
|
91
|
+
def fails?(command)
|
92
|
+
!succeeds?(command)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Escape some text for the shell and enclose it in single quotes
|
96
|
+
# if necessary.
|
97
|
+
def shell_escape(s)
|
98
|
+
s = s.to_s
|
99
|
+
if s !~ /^[0-9A-Za-z+,.\/:=@_-]+$/
|
100
|
+
s = s.gsub("'") { "'\\''" }
|
101
|
+
s = "'#{s}'"
|
102
|
+
end
|
103
|
+
s
|
104
|
+
end
|
105
|
+
|
106
|
+
# Like mkdir -p. Optionally, set the owner and mode.
|
107
|
+
def mkdir(dir, owner = nil, mode = nil)
|
108
|
+
FileUtils.mkdir_p(dir, :verbose => verbose?)
|
109
|
+
chmod(dir, mode) if mode
|
110
|
+
chown(dir, owner) if owner
|
111
|
+
end
|
112
|
+
|
113
|
+
# mkdir only if the directory doesn't already exist. Optionally,
|
114
|
+
# set the owner and mode.
|
115
|
+
def mkdir_if_necessary(dir, owner = nil, mode = nil)
|
116
|
+
mkdir(dir, owner, mode) if !(File.exists?(dir) || File.symlink?(dir))
|
117
|
+
end
|
118
|
+
|
119
|
+
# rm a dir and recreate it.
|
120
|
+
def rm_and_mkdir(dir)
|
121
|
+
raise "don't do this" if dir == ""
|
122
|
+
run "rm -rf #{dir} && mkdir -p #{dir}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Are two files different?
|
126
|
+
def different?(a, b)
|
127
|
+
!FileUtils.compare_file(a, b)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Copy perms from src file to dst.
|
131
|
+
def copy_perms(src, dst)
|
132
|
+
stat = File.stat(src)
|
133
|
+
File.chmod(stat.mode, dst)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Copy perms and timestamps from src file to dst.
|
137
|
+
def copy_metadata(src, dst)
|
138
|
+
stat = File.stat(src)
|
139
|
+
File.chmod(stat.mode, dst)
|
140
|
+
File.utime(stat.atime, stat.mtime, dst)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Copy file or dir from src to dst. Optionally, set the mode and
|
144
|
+
# owner of dst.
|
145
|
+
def cp(src, dst, owner = nil, mode = nil)
|
146
|
+
FileUtils.cp_r(src, dst, :preserve => true, :verbose => verbose?)
|
147
|
+
if owner && !File.symlink?(dst)
|
148
|
+
chown(dst, owner)
|
149
|
+
end
|
150
|
+
if mode
|
151
|
+
chmod(dst, mode)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Copy file or dir from src to dst, but create the dst directory
|
156
|
+
# first if necessary. Optionally, set the mode and owner of dst.
|
157
|
+
def cp_with_mkdir(src, dst, owner = nil, mode = nil)
|
158
|
+
mkdir_if_necessary(File.dirname(dst))
|
159
|
+
cp(src, dst, owner, mode)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Copy file or dir from src to dst, but ONLY if dst doesn't exist
|
163
|
+
# or has different contents than src. Optionally, set the mode and
|
164
|
+
# owner of dst.
|
165
|
+
def cp_if_necessary(src, dst, owner = nil, mode = nil)
|
166
|
+
if !File.exists?(dst) || different?(src, dst)
|
167
|
+
cp(src, dst, owner, mode)
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Move src to dst. Because this uses FileUtils, it works even if
|
173
|
+
# dst is on a different partition.
|
174
|
+
def mv(src, dst)
|
175
|
+
FileUtils.mv(src, dst, :verbose => verbose?)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Move src to dst, but create the dst directory first if
|
179
|
+
# necessary.
|
180
|
+
def mv_with_mkdir(src, dst)
|
181
|
+
mkdir_if_necessary(File.dirname(dst))
|
182
|
+
mv(src, dst)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Chown file to be owned by user.
|
186
|
+
def chown(file, user)
|
187
|
+
user = user.to_s
|
188
|
+
# who is the current owner?
|
189
|
+
@uids ||= {}
|
190
|
+
@uids[user] ||= Etc.getpwnam(user).uid
|
191
|
+
uid = @uids[user]
|
192
|
+
if File.stat(file).uid != uid
|
193
|
+
run "chown #{user}:#{user} '#{file}'"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Chmod file to a new mode.
|
198
|
+
def chmod(file, mode)
|
199
|
+
if File.stat(file).mode != mode
|
200
|
+
FileUtils.chmod(mode, file, :verbose => verbose?)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# rm a file
|
205
|
+
def rm(file)
|
206
|
+
FileUtils.rm(file, :force => true, :verbose => verbose?)
|
207
|
+
end
|
208
|
+
|
209
|
+
# rm a file, but only if it exists.
|
210
|
+
def rm_if_necessary(file)
|
211
|
+
if File.exists?(file)
|
212
|
+
rm(file)
|
213
|
+
true
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Create a symlink from src to dst.
|
218
|
+
def ln(src, dst)
|
219
|
+
FileUtils.ln_sf(src, dst, :verbose => verbose?)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Create a symlink from src to dst, but only if it hasn't already
|
223
|
+
# been created.
|
224
|
+
def ln_if_necessary(src, dst)
|
225
|
+
ln = false
|
226
|
+
if !File.symlink?(dst)
|
227
|
+
ln = true
|
228
|
+
elsif File.readlink(dst) != src
|
229
|
+
rm(dst)
|
230
|
+
ln = true
|
231
|
+
end
|
232
|
+
if ln
|
233
|
+
ln(src, dst)
|
234
|
+
true
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# A nice printout in green.
|
239
|
+
def banner(s, color = GREEN)
|
240
|
+
s = "#{s} ".ljust(72, " ")
|
241
|
+
$stderr.write "#{color}[#{Time.new.strftime('%H:%M:%S')}] #{s}#{RESET}\n"
|
242
|
+
$stderr.flush
|
243
|
+
end
|
244
|
+
|
245
|
+
# Print a warning in yellow.
|
246
|
+
def warning(msg)
|
247
|
+
banner("Warning: #{msg}", YELLOW)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Print a fatal error in red, then exit.
|
251
|
+
def fatal(msg)
|
252
|
+
banner(msg, RED)
|
253
|
+
exit(1)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Who owns this process?
|
257
|
+
def whoami
|
258
|
+
@whoami ||= Etc.getpwuid(Process.uid).name
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns true if the pkg is installed.
|
262
|
+
def package_is_installed?(pkg)
|
263
|
+
succeeds?("dpkg-query -f='${Status}' -W #{pkg} | grep 'install ok installed' 2> /dev/null")
|
264
|
+
end
|
265
|
+
|
266
|
+
# Install pkg if necessary.
|
267
|
+
def package_if_necessary(pkg)
|
268
|
+
if !package_is_installed?(pkg)
|
269
|
+
banner "#{pkg}..."
|
270
|
+
run "apt-get -y install #{pkg}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Install gem if necessary.
|
275
|
+
def gem_if_necessary(gem)
|
276
|
+
grep = args = nil
|
277
|
+
if gem =~ /(.*)-(\d+\.\d+\.\d+)$/
|
278
|
+
gem, version = $1, $2
|
279
|
+
grep = "^#{gem}.*#{version}"
|
280
|
+
args = " --version #{version}"
|
281
|
+
else
|
282
|
+
grep = "^#{gem}"
|
283
|
+
end
|
284
|
+
if fails?("gem list #{gem} | grep '#{grep}'")
|
285
|
+
banner "#{gem}..."
|
286
|
+
run "gem install #{gem} #{args} --no-rdoc --no-ri"
|
287
|
+
return true
|
288
|
+
end
|
289
|
+
false
|
290
|
+
end
|
291
|
+
|
292
|
+
# Returns true if the pidfile exists and that process id exists as
|
293
|
+
# well.
|
294
|
+
def process_by_pid?(pidfile)
|
295
|
+
begin
|
296
|
+
if File.exists?(pidfile)
|
297
|
+
pid = File.read(pidfile).to_i
|
298
|
+
if pid != 0
|
299
|
+
Process.kill(0, pid)
|
300
|
+
return true
|
301
|
+
end
|
302
|
+
end
|
303
|
+
rescue Errno::ENOENT, Errno::ESRCH
|
304
|
+
end
|
305
|
+
false
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
# Returns true if verbosity is turned on.
|
311
|
+
def verbose?
|
312
|
+
@run_verbose ||= nil
|
313
|
+
end
|
314
|
+
|
315
|
+
def vputs(s)
|
316
|
+
$stderr.puts s if verbose?
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
data/teleport.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "teleport/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "teleport"
|
6
|
+
s.version = Teleport::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Adam Doppelt"]
|
9
|
+
s.email = ["amd@gurge.com"]
|
10
|
+
s.homepage = "http://github.com/rglabs/teleport"
|
11
|
+
s.summary = %Q{Teleport - opinionated Ubuntu server setup with Ruby.}
|
12
|
+
s.description = %Q{Easy Ubuntu server setup via teleportation.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "teleport"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: teleport
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adam Doppelt
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-03 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Easy Ubuntu server setup via teleportation.
|
23
|
+
email:
|
24
|
+
- amd@gurge.com
|
25
|
+
executables:
|
26
|
+
- teleport
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- bin/teleport
|
38
|
+
- lib/teleport.rb
|
39
|
+
- lib/teleport/config.rb
|
40
|
+
- lib/teleport/constants.rb
|
41
|
+
- lib/teleport/install.rb
|
42
|
+
- lib/teleport/main.rb
|
43
|
+
- lib/teleport/mirror.rb
|
44
|
+
- lib/teleport/run.sh
|
45
|
+
- lib/teleport/util.rb
|
46
|
+
- lib/teleport/version.rb
|
47
|
+
- teleport.gemspec
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/rglabs/teleport
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: teleport
|
78
|
+
rubygems_version: 1.5.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Teleport - opinionated Ubuntu server setup with Ruby.
|
82
|
+
test_files: []
|
83
|
+
|