rails-app-installer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +32 -0
- data/examples/installer +19 -0
- data/examples/rails_installer_defaults.yml +5 -0
- data/examples/typo +37 -0
- data/lib/apache13.conf.example.template +33 -0
- data/lib/apache20.conf.example.template +40 -0
- data/lib/lighttpd.conf.example.template +6 -0
- data/lib/rails-installer.rb +575 -0
- data/lib/rails-installer/commands.rb +187 -0
- data/lib/rails-installer/databases.rb +222 -0
- data/lib/rails-installer/web-servers.rb +115 -0
- metadata +54 -0
data/README
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
This is an installer for Rails applications. It's designed to allow users to
|
2
|
+
install Rails apps onto their own systems with a minimum amount of effort.
|
3
|
+
|
4
|
+
This installer was originally part of Typo (http://typosphere.org).
|
5
|
+
|
6
|
+
Adding the installer to your Rails app
|
7
|
+
--------------------------------------
|
8
|
+
|
9
|
+
To add the installer to your application, copy the `installer` file from the
|
10
|
+
examples directory into your project's `bin` directory, and rename it to match
|
11
|
+
your application name. For example, Typo's installer lives in `bin/typo`. Then
|
12
|
+
modify your installer as needed, customizing the application name, support
|
13
|
+
address, and required Rails version. Once this is complete, create an
|
14
|
+
`installer` directory for your app and copy
|
15
|
+
`examples/rails_installer_defaults.yml` into it. You can probably use it
|
16
|
+
unchanged.
|
17
|
+
|
18
|
+
You should now be able to test the installer like this:
|
19
|
+
|
20
|
+
$ ruby ./bin/my_installer install /tmp/foo cwd
|
21
|
+
|
22
|
+
This will try to install your app in `/tmp/foo` using the current directory as
|
23
|
+
a template. If you leave off the `cwd` option at the end, then the installer will look for the most recent Ruby GEM for your app, using the `application_name` line from the installer as the gem name.
|
24
|
+
|
25
|
+
Creating a Gem
|
26
|
+
--------------
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
Using the installer
|
31
|
+
-------------------
|
32
|
+
|
data/examples/installer
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rails-installer'
|
5
|
+
|
6
|
+
class AppInstaller < RailsInstaller
|
7
|
+
application_name 'my_shiny_metal_app'
|
8
|
+
support_location 'our shiny website'
|
9
|
+
rails_version '1.1.4'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Installer program
|
13
|
+
directory = ARGV[1]
|
14
|
+
|
15
|
+
app = AppInstaller.new(directory)
|
16
|
+
app.message_proc = Proc.new do |msg|
|
17
|
+
STDERR.puts " #{msg}"
|
18
|
+
end
|
19
|
+
app.execute_command(*ARGV)
|
data/examples/typo
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rails-installer'
|
4
|
+
|
5
|
+
class TypoInstaller < RailsInstaller
|
6
|
+
application_name 'typo'
|
7
|
+
support_location 'the Typo mailing list'
|
8
|
+
rails_version '1.1.4'
|
9
|
+
|
10
|
+
def install_post_hook
|
11
|
+
sweep_cache
|
12
|
+
end
|
13
|
+
|
14
|
+
# Sweep the cache
|
15
|
+
def sweep_cache
|
16
|
+
Dir.chdir(install_directory)
|
17
|
+
message "Cleaning out #{@@app_name.capitalize}'s cache"
|
18
|
+
status = system("rake -s sweep_cache > /dev/null 2> /dev/null")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SweepCache < RailsInstaller::Command
|
23
|
+
help "Sweep Typo's cache"
|
24
|
+
|
25
|
+
def self.command(installer, *args)
|
26
|
+
installer.sweep_cache
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Installer program
|
31
|
+
directory = ARGV[1]
|
32
|
+
|
33
|
+
typo = TypoInstaller.new(directory)
|
34
|
+
typo.message_proc = Proc.new do |msg|
|
35
|
+
STDERR.puts " #{msg}"
|
36
|
+
end
|
37
|
+
typo.execute_command(*ARGV)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Apache 1.3 HTTP proxy example for Typo with Mongrel
|
2
|
+
#
|
3
|
+
# Before any of this will work, you need to make sure that the mod_proxy
|
4
|
+
# modules are loaded. For shared hosting, your provider will have do this
|
5
|
+
# globally. The line required looks like this, although the exact path may
|
6
|
+
# vary:
|
7
|
+
#
|
8
|
+
# LoadModule proxy_module /usr/lib/apache/modules/mod_proxy.so
|
9
|
+
#
|
10
|
+
# Then you'll want a VirtualHost section that looks about like this:
|
11
|
+
|
12
|
+
<VirtualHost blog.example.com>
|
13
|
+
ServerName blog.example.com
|
14
|
+
ServerAlias www.blog.example.com
|
15
|
+
|
16
|
+
# Change this to your email address
|
17
|
+
ServerAdmin webmaster@localhost
|
18
|
+
|
19
|
+
# Change these to be valid paths for your host. The DocumentRoot path
|
20
|
+
# isn't very important because we don't actually use it for anything.
|
21
|
+
# For security's sake, it's best that it points to an empty directory,
|
22
|
+
# but that's not critical.
|
23
|
+
DocumentRoot /var/www/blog
|
24
|
+
ErrorLog /var/log/apache2/blog_error.log
|
25
|
+
CustomLog /var/log/apache2/blog_access.log combined
|
26
|
+
|
27
|
+
ServerSignature On
|
28
|
+
|
29
|
+
# This is the important part--it sets up proxying.
|
30
|
+
ProxyRequests Off
|
31
|
+
ProxyPass / $RAILS_URL
|
32
|
+
ProxyPassReverse / $RAILS_URL
|
33
|
+
</VirtualHost>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Apache 2.0/2.2 HTTP proxy example for Typo with Mongrel
|
2
|
+
#
|
3
|
+
# Before any of this will work, you need to make sure that the mod_proxy and
|
4
|
+
# mod_proxy_http modules are loaded. For shared hosting, your provider will
|
5
|
+
# have do this globally. The two lines required look like this, although the
|
6
|
+
# exact path may vary:
|
7
|
+
#
|
8
|
+
# LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
|
9
|
+
# LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
|
10
|
+
#
|
11
|
+
# Then you'll want a VirtualHost section that looks about like this:
|
12
|
+
|
13
|
+
<VirtualHost blog.example.com>
|
14
|
+
ServerName blog.example.com
|
15
|
+
ServerAlias www.blog.example.com
|
16
|
+
|
17
|
+
# Change this to your email address
|
18
|
+
ServerAdmin webmaster@localhost
|
19
|
+
|
20
|
+
# Change these to be valid paths for your host. The DocumentRoot path
|
21
|
+
# isn't very important because we don't actually use it for anything.
|
22
|
+
# For security's sake, it's best that it points to an empty directory,
|
23
|
+
# but that's not critical.
|
24
|
+
DocumentRoot /var/www/blog
|
25
|
+
ErrorLog /var/log/apache2/blog_error.log
|
26
|
+
CustomLog /var/log/apache2/blog_access.log combined
|
27
|
+
|
28
|
+
ServerSignature On
|
29
|
+
|
30
|
+
# This is the important part--it sets up proxying.
|
31
|
+
ProxyRequests Off
|
32
|
+
<Proxy *>
|
33
|
+
Order deny,allow
|
34
|
+
Allow from all
|
35
|
+
</Proxy>
|
36
|
+
|
37
|
+
ProxyPass / $RAILS_URL
|
38
|
+
ProxyPassReverse / $RAILS_URL
|
39
|
+
ProxyPreserveHost On
|
40
|
+
</VirtualHost>
|
@@ -0,0 +1,575 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
require 'rails-installer/databases'
|
8
|
+
require 'rails-installer/web-servers'
|
9
|
+
require 'rails-installer/commands'
|
10
|
+
|
11
|
+
#
|
12
|
+
# An installer for Rails applications.
|
13
|
+
#
|
14
|
+
# The Rails Application Installer is designed to make it easy for end-users to
|
15
|
+
# install open-source Rails apps with a minimum amount of effort. When built
|
16
|
+
# properly, all the user needs to do is run:
|
17
|
+
#
|
18
|
+
# $ gem install my_app
|
19
|
+
# $ my_app install /some/path
|
20
|
+
#
|
21
|
+
# To use this installer, you'll need to create a small driver program (the
|
22
|
+
# 'my_app' program from above). Here's a minimal example:
|
23
|
+
#
|
24
|
+
# #!/usr/bin/env ruby
|
25
|
+
#
|
26
|
+
# require 'rubygems'
|
27
|
+
# require 'rails-installer'
|
28
|
+
#
|
29
|
+
# class AppInstaller < RailsInstaller
|
30
|
+
# application_name 'my_shiny_metal_app'
|
31
|
+
# support_location 'our shiny website'
|
32
|
+
# rails_version '1.1.4'
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # Installer program
|
36
|
+
# directory = ARGV[1]
|
37
|
+
#
|
38
|
+
# app = AppInstaller.new(directory)
|
39
|
+
# app.message_proc = Proc.new do |msg|
|
40
|
+
# STDERR.puts " #{msg}"
|
41
|
+
# end
|
42
|
+
# app.execute_command(*ARGV)
|
43
|
+
#
|
44
|
+
# Place this in your application's gem/ directory, and then add it to your
|
45
|
+
# .gem using the 'executables' gemspec option. See the examples/ directory for
|
46
|
+
# more complex examples.
|
47
|
+
class RailsInstaller
|
48
|
+
include FileUtils
|
49
|
+
attr_accessor :install_directory, :source_directory, :config
|
50
|
+
attr_accessor :message_proc
|
51
|
+
|
52
|
+
class InstallFailed < StandardError; end
|
53
|
+
|
54
|
+
@@rails_version = nil
|
55
|
+
|
56
|
+
# The application name. Set this in your derived class.
|
57
|
+
def self.application_name(name)
|
58
|
+
@@app_name = name
|
59
|
+
end
|
60
|
+
|
61
|
+
# The support location. This is displayed to the user at the end of the
|
62
|
+
# install process.
|
63
|
+
def self.support_location(location)
|
64
|
+
@@support_location = location
|
65
|
+
end
|
66
|
+
|
67
|
+
# Which Rails version this app needs. This version of Rails will be frozen
|
68
|
+
# into vendor/rails/
|
69
|
+
def self.rails_version(svn_tag)
|
70
|
+
@@rails_version = svn_tag
|
71
|
+
end
|
72
|
+
|
73
|
+
# The application name, as set by +application_name+.
|
74
|
+
def app_name
|
75
|
+
@@app_name
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize(install_directory)
|
79
|
+
# use an absolute path, not a relative path.
|
80
|
+
if install_directory
|
81
|
+
@install_directory = File.expand_path(install_directory)
|
82
|
+
end
|
83
|
+
|
84
|
+
@config = read_yml(config_file) rescue nil
|
85
|
+
@config ||= Hash.new
|
86
|
+
end
|
87
|
+
|
88
|
+
# Display a status message
|
89
|
+
def message(string)
|
90
|
+
if message_proc
|
91
|
+
message_proc.call(string)
|
92
|
+
else
|
93
|
+
STDERR.puts string
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Install Application
|
98
|
+
def install(version=nil)
|
99
|
+
@source_directory = find_source_directory(@@app_name,version)
|
100
|
+
|
101
|
+
# Merge default configuration settings
|
102
|
+
@config = read_yml(backup_config_file).merge(config)
|
103
|
+
|
104
|
+
install_sequence
|
105
|
+
|
106
|
+
message ''
|
107
|
+
message "#{@@app_name.capitalize} is now running on http://#{`hostname`.chomp}:#{config['port-number']}"
|
108
|
+
message "Use '#{@@app_name} start #{install_directory}' to restart after boot."
|
109
|
+
message "Look in installer/*.conf.example to see how to integrate with your web server."
|
110
|
+
end
|
111
|
+
|
112
|
+
# The default install sequence. Override this if you need to add extra
|
113
|
+
# steps to the installer.
|
114
|
+
def install_sequence
|
115
|
+
stop
|
116
|
+
|
117
|
+
backup_database
|
118
|
+
install_pre_hook
|
119
|
+
pre_migrate_database
|
120
|
+
copy_files
|
121
|
+
freeze_rails
|
122
|
+
create_default_config_files
|
123
|
+
fix_permissions
|
124
|
+
create_directories
|
125
|
+
create_initial_database
|
126
|
+
set_initial_port_number
|
127
|
+
expand_template_files
|
128
|
+
|
129
|
+
migrate
|
130
|
+
install_post_hook
|
131
|
+
save
|
132
|
+
|
133
|
+
run_rails_tests
|
134
|
+
|
135
|
+
start
|
136
|
+
end
|
137
|
+
|
138
|
+
# The easy way to add steps to the installation process. +install_pre_hook+
|
139
|
+
# runs right after the DB is backed up and right before the first migration
|
140
|
+
# attempt.
|
141
|
+
def install_pre_hook
|
142
|
+
end
|
143
|
+
|
144
|
+
# Another install hook; +install_post_hook+ runs after the final migration.
|
145
|
+
def install_post_hook
|
146
|
+
end
|
147
|
+
|
148
|
+
# Start application in the background
|
149
|
+
def start(foreground = false)
|
150
|
+
server_class = RailsInstaller::WebServer.servers[config['web-server']]
|
151
|
+
if not server_class
|
152
|
+
message "** warning: web-server #{config['web-server']} unknown. Use 'web-server=external' to disable."
|
153
|
+
end
|
154
|
+
|
155
|
+
server_class.start(self,foreground)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Stop application
|
159
|
+
def stop
|
160
|
+
return unless File.directory?(install_directory)
|
161
|
+
|
162
|
+
server_class = RailsInstaller::WebServer.servers[config['web-server']]
|
163
|
+
if not server_class
|
164
|
+
message "** warning: web-server #{config['web-server']} unknown. Use 'web-server=external' to disable."
|
165
|
+
end
|
166
|
+
|
167
|
+
server_class.stop(self)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Backup the database
|
171
|
+
def backup_database
|
172
|
+
db_class = RailsInstaller::Database.dbs[config['database']]
|
173
|
+
db_class.backup(self)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Restore the database
|
177
|
+
def restore_database(filename)
|
178
|
+
db_class = RailsInstaller::Database.dbs[config['database']]
|
179
|
+
in_directory install_directory do
|
180
|
+
db_class.restore(self, filename)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Copy files from the source directory to the target directory.
|
185
|
+
def copy_files
|
186
|
+
message "Checking for existing #{@@app_name.capitalize} install in #{install_directory}"
|
187
|
+
files_yml = File.join(install_directory,'installer','files.yml')
|
188
|
+
old_files = read_yml(files_yml) rescue Hash.new
|
189
|
+
|
190
|
+
message "Reading files from #{source_directory}"
|
191
|
+
new_files = sha1_hash_directory_tree(source_directory)
|
192
|
+
new_files.delete('/config/database.yml') # Never copy this.
|
193
|
+
|
194
|
+
# Next, we compare the original install hash to the current hash. For each
|
195
|
+
# entry:
|
196
|
+
#
|
197
|
+
# - in new_file but not in old_files: copy
|
198
|
+
# - in old files but not in new_files: delete
|
199
|
+
# - in both, but hash different: copy
|
200
|
+
# - in both, hash same: don't copy
|
201
|
+
#
|
202
|
+
# We really should add a third hash (existing_files) and compare against that
|
203
|
+
# so we don't overwrite changed files.
|
204
|
+
|
205
|
+
added, changed, deleted, same = hash_diff(old_files, new_files)
|
206
|
+
|
207
|
+
if added.size > 0
|
208
|
+
message "Copying #{added.size} new files into #{install_directory}"
|
209
|
+
added.keys.sort.each do |file|
|
210
|
+
message " copying #{file}"
|
211
|
+
copy_one_file(file)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
if changed.size > 0
|
216
|
+
message "Updating #{changed.size} files in #{install_directory}"
|
217
|
+
changed.keys.sort.each do |file|
|
218
|
+
message " updating #{file}"
|
219
|
+
copy_one_file(file)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
if deleted.size > 0
|
224
|
+
message "Deleting #{deleted.size} files from #{install_directory}"
|
225
|
+
|
226
|
+
deleted.keys.sort.each do |file|
|
227
|
+
message " deleting #{file}"
|
228
|
+
rm(File.join(install_directory,file))
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
write_yml(files_yml,new_files)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Copy one file from source_directory to install_directory, creating
|
236
|
+
# directories as needed.
|
237
|
+
def copy_one_file(filename)
|
238
|
+
source_name = File.join(source_directory,filename)
|
239
|
+
install_name = File.join(install_directory,filename)
|
240
|
+
dir_name = File.dirname(install_name)
|
241
|
+
|
242
|
+
mkdir_p(dir_name)
|
243
|
+
cp(source_name,install_name,:preserve => true)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Compute the different between two hashes. Returns four hashes,
|
247
|
+
# one contains the keys that are in 'b' but not in 'a' (added entries),
|
248
|
+
# the next contains keys that are in 'a' and 'b', but have different values
|
249
|
+
# (changed). The third contains keys that are in 'b' but not 'a' (added).
|
250
|
+
# The final hash contains items that are the same in each.
|
251
|
+
def hash_diff(a, b)
|
252
|
+
added = {}
|
253
|
+
changed = {}
|
254
|
+
deleted = {}
|
255
|
+
same = {}
|
256
|
+
|
257
|
+
seen = {}
|
258
|
+
|
259
|
+
a.each_key do |k|
|
260
|
+
seen[k] = true
|
261
|
+
|
262
|
+
if b.has_key? k
|
263
|
+
if b[k] == a[k]
|
264
|
+
same[k] = true
|
265
|
+
else
|
266
|
+
changed[k] = true
|
267
|
+
end
|
268
|
+
else
|
269
|
+
deleted[k] = true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
b.each_key do |k|
|
274
|
+
unless seen[k]
|
275
|
+
added[k] = true
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
[added, changed, deleted, same]
|
280
|
+
end
|
281
|
+
|
282
|
+
# Freeze to a specific version of Rails gems.
|
283
|
+
def freeze_rails
|
284
|
+
return unless @@rails_version
|
285
|
+
version_file = File.join(install_directory,'vendor','rails-version')
|
286
|
+
vendor_rails = File.join(install_directory,'vendor','rails')
|
287
|
+
|
288
|
+
old_version = File.read(version_file).chomp rescue nil
|
289
|
+
|
290
|
+
if @@rails_version == old_version
|
291
|
+
return
|
292
|
+
elsif old_version != nil
|
293
|
+
rm_rf(vendor_rails)
|
294
|
+
end
|
295
|
+
|
296
|
+
mkdir_p(vendor_rails)
|
297
|
+
|
298
|
+
package_map = {
|
299
|
+
'rails' => File.join(vendor_rails,'railties'),
|
300
|
+
'actionmailer' => File.join(vendor_rails,'actionmailer'),
|
301
|
+
'actionpack' => File.join(vendor_rails,'actionpack'),
|
302
|
+
'actionwebservice' => File.join(vendor_rails,'actionwebservice'),
|
303
|
+
'activerecord' => File.join(vendor_rails,'activerecord'),
|
304
|
+
'activesupport' => File.join(vendor_rails,'activesupport'),
|
305
|
+
}
|
306
|
+
|
307
|
+
specs = Gem.source_index.find_name('rails',["= #{@@rails_version}"])
|
308
|
+
|
309
|
+
unless specs.to_a.size > 0
|
310
|
+
raise InstallFailed, "Can't locate Rails #{@@rails_version}!"
|
311
|
+
end
|
312
|
+
|
313
|
+
copy_gem(specs.first, package_map[specs.first.name])
|
314
|
+
|
315
|
+
specs.first.dependencies.each do |dep|
|
316
|
+
next unless package_map[dep.name]
|
317
|
+
|
318
|
+
dep_spec = Gem.source_index.find_name(dep.name,[dep.version_requirements.to_s])
|
319
|
+
if dep_spec.size == 0
|
320
|
+
raise InstallFailed, "Can't locate dependency #{dep.name} #{dep.version_requirements.to_s}"
|
321
|
+
end
|
322
|
+
|
323
|
+
copy_gem(dep_spec.first, package_map[dep.name])
|
324
|
+
end
|
325
|
+
|
326
|
+
File.open(version_file,'w') do |f|
|
327
|
+
f.puts @@rails_version
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# Copy a specific gem's contents.
|
332
|
+
def copy_gem(spec, destination)
|
333
|
+
message("copying #{spec.name} #{spec.version} to #{destination}")
|
334
|
+
cp_r("#{spec.full_gem_path}/.",destination)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Create all default config files
|
338
|
+
def create_default_config_files
|
339
|
+
create_default_database_yml
|
340
|
+
end
|
341
|
+
|
342
|
+
# Create the default database.yml
|
343
|
+
def create_default_database_yml
|
344
|
+
db_class = RailsInstaller::Database.dbs[config['database']]
|
345
|
+
db_class.database_yml(self)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Clean up file and directory permissions.
|
349
|
+
def fix_permissions
|
350
|
+
unless RUBY_PLATFORM =~ /mswin32/
|
351
|
+
message "Making scripts executable"
|
352
|
+
chmod 0555, File.join(install_directory,'public','dispatch.fcgi')
|
353
|
+
chmod 0555, File.join(install_directory,'public','dispatch.cgi')
|
354
|
+
chmod 0555, Dir[File.join(install_directory,'script','*')]
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Create required directories, like tmp
|
359
|
+
def create_directories
|
360
|
+
mkdir_p(File.join(install_directory,'tmp','cache'))
|
361
|
+
chmod(0755, File.join(install_directory,'tmp','cache'))
|
362
|
+
mkdir_p(File.join(install_directory,'tmp','session'))
|
363
|
+
mkdir_p(File.join(install_directory,'tmp','sockets'))
|
364
|
+
mkdir_p(File.join(install_directory,'log'))
|
365
|
+
File.open(File.join(install_directory,'log','development.log'),'w')
|
366
|
+
File.open(File.join(install_directory,'log','production.log'),'w')
|
367
|
+
File.open(File.join(install_directory,'log','testing.log'),'w')
|
368
|
+
end
|
369
|
+
|
370
|
+
# Create the initial SQLite database
|
371
|
+
def create_initial_database
|
372
|
+
db_class = RailsInstaller::Database.dbs[config['database']]
|
373
|
+
in_directory(install_directory) do
|
374
|
+
db_class.create(self)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Get the current schema version
|
379
|
+
def get_schema_version
|
380
|
+
File.read(File.join(install_directory,'db','schema_version')).to_i rescue 0
|
381
|
+
end
|
382
|
+
|
383
|
+
# The path to the installed config file
|
384
|
+
def config_file
|
385
|
+
File.join(install_directory,'installer','rails_installer.yml')
|
386
|
+
end
|
387
|
+
|
388
|
+
# The path to the config file that comes with the GEM
|
389
|
+
def backup_config_file
|
390
|
+
File.join(source_directory,'installer','rails_installer_defaults.yml')
|
391
|
+
end
|
392
|
+
|
393
|
+
# Pick a default port number
|
394
|
+
def set_initial_port_number
|
395
|
+
config['port-number'] ||= (rand(1000)+4000)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Pre-migrate the database. This checks to see if we're downgrading to an
|
399
|
+
# earlier version of our app, and runs 'rake migrate VERSION=...' to
|
400
|
+
# downgrade the database.
|
401
|
+
def pre_migrate_database
|
402
|
+
old_schema_version = get_schema_version
|
403
|
+
new_schema_version = File.read(File.join(source_directory,'db','schema_version')).to_i
|
404
|
+
|
405
|
+
return unless old_schema_version > 0
|
406
|
+
|
407
|
+
# Are we downgrading?
|
408
|
+
if old_schema_version > new_schema_version
|
409
|
+
message "Downgrading schema from #{old_schema_version} to #{new_schema_version}"
|
410
|
+
|
411
|
+
in_directory install_directory do
|
412
|
+
unless system("rake -s migrate VERSION=#{new_schema_version}")
|
413
|
+
raise InstallFailed, "Downgrade migrating from #{old_schema_version} to #{new_schema_version} failed."
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# Migrate the database
|
420
|
+
def migrate
|
421
|
+
message "Migrating #{@@app_name.capitalize}'s database to newest release"
|
422
|
+
|
423
|
+
in_directory install_directory do
|
424
|
+
unless system("rake -s migrate")
|
425
|
+
raise InstallFailed, "Migration failed"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# Run Rails tests. This helps verify that we have a clean install
|
431
|
+
# with all dependencies. This cuts down on a lot of bug reports.
|
432
|
+
def run_rails_tests
|
433
|
+
message "Running tests. This may take a minute or two"
|
434
|
+
|
435
|
+
in_directory install_directory do
|
436
|
+
if system_silently("rake -s test")
|
437
|
+
message "All tests pass. Congratulations."
|
438
|
+
else
|
439
|
+
message "***** Tests failed *****"
|
440
|
+
message "** Please run 'rake test' by hand in your install directory."
|
441
|
+
message "** Report problems to #{@@support_location}."
|
442
|
+
message "***** Tests failed *****"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Find all files in a directory tree and return a Hash containing sha1
|
448
|
+
# hashes of all files.
|
449
|
+
def sha1_hash_directory_tree(directory, prefix='', hash={})
|
450
|
+
Dir.entries(directory).each do |file|
|
451
|
+
next if file =~ /^\./
|
452
|
+
pathname = File.join(directory,file)
|
453
|
+
if File.directory?(pathname)
|
454
|
+
sha1_hash_directory_tree(pathname, File.join(prefix,file), hash)
|
455
|
+
else
|
456
|
+
hash[File.join(prefix,file)] = Digest::SHA1.hexdigest(File.read(pathname))
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
hash
|
461
|
+
end
|
462
|
+
|
463
|
+
# Save config settings
|
464
|
+
def save
|
465
|
+
write_yml(config_file,@config)
|
466
|
+
end
|
467
|
+
|
468
|
+
# Load a yaml file
|
469
|
+
def read_yml(filename)
|
470
|
+
YAML.load(File.read(filename))
|
471
|
+
end
|
472
|
+
|
473
|
+
# Save a yaml file.
|
474
|
+
def write_yml(filename,object)
|
475
|
+
File.open(filename,'w') do |f|
|
476
|
+
f.write(YAML.dump(object))
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
# Locate the source directory for a specific Version
|
481
|
+
def find_source_directory(gem_name, version)
|
482
|
+
if version == 'cwd'
|
483
|
+
return Dir.pwd
|
484
|
+
elsif version
|
485
|
+
version_array = ["= #{version}"]
|
486
|
+
else
|
487
|
+
version_array = ["> 0.0.0"]
|
488
|
+
end
|
489
|
+
|
490
|
+
specs = Gem.source_index.find_name(gem_name,version_array)
|
491
|
+
unless specs.to_a.size > 0
|
492
|
+
raise InstallFailed, "Can't locate version #{version}!"
|
493
|
+
end
|
494
|
+
|
495
|
+
specs.last.full_gem_path
|
496
|
+
end
|
497
|
+
|
498
|
+
# Call +system+, ignoring all output.
|
499
|
+
def system_silently(command)
|
500
|
+
if RUBY_PLATFORM =~ /mswin32/
|
501
|
+
null = 'NUL:'
|
502
|
+
else
|
503
|
+
null = '/dev/null'
|
504
|
+
end
|
505
|
+
|
506
|
+
system("#{command} > #{null} 2> #{null}")
|
507
|
+
end
|
508
|
+
|
509
|
+
# Expand configuration template files.
|
510
|
+
def expand_template_files
|
511
|
+
rails_host = config['bind-address'] || `hostname`.chomp
|
512
|
+
rails_port = config['port-number'].to_s
|
513
|
+
rails_url = "http://#{rails_host}:#{rails_port}"
|
514
|
+
Dir[File.join(install_directory,'installer','*.template')].each do |template_file|
|
515
|
+
output_file = template_file.gsub(/\.template/,'')
|
516
|
+
next if File.exists?(output_file) # don't overwrite files
|
517
|
+
|
518
|
+
message "expanding #{File.basename(output_file)} template"
|
519
|
+
|
520
|
+
text = File.read(template_file).gsub(/\$RAILS_URL/,rails_url).gsub(/\$RAILS_HOST/,rails_host).gsub(/\$RAILS_PORT/,rails_port)
|
521
|
+
|
522
|
+
File.open(output_file,'w') do |f|
|
523
|
+
f.write text
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
# Execute a command-line command
|
529
|
+
def execute_command(*args)
|
530
|
+
if args.size < 2
|
531
|
+
display_help
|
532
|
+
exit(1)
|
533
|
+
end
|
534
|
+
|
535
|
+
command_class = Command.commands[args.first]
|
536
|
+
|
537
|
+
if command_class
|
538
|
+
command_class.command(self,*(args[2..-1]))
|
539
|
+
else
|
540
|
+
display_help
|
541
|
+
exit(1)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
# Display help.
|
546
|
+
def display_help(error=nil)
|
547
|
+
STDERR.puts error if error
|
548
|
+
|
549
|
+
commands = Command.commands.keys.sort
|
550
|
+
commands.each do |cmd|
|
551
|
+
cmd_class = Command.commands[cmd]
|
552
|
+
flag_help = cmd_class.flag_help_text.gsub(/APPNAME/,app_name)
|
553
|
+
help = cmd_class.help_text.gsub(/APPNAME/,app_name)
|
554
|
+
|
555
|
+
STDERR.puts " #{app_name} #{cmd} DIRECTORY #{flag_help}"
|
556
|
+
STDERR.puts " #{help}"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
# Run a block inside of a specific directory. Chdir into the directory
|
562
|
+
# before executing the block, then chdir back to the original directory
|
563
|
+
# when the block exits.
|
564
|
+
def in_directory(directory)
|
565
|
+
begin
|
566
|
+
old_dir = Dir.pwd
|
567
|
+
Dir.chdir(directory)
|
568
|
+
value = yield
|
569
|
+
ensure
|
570
|
+
Dir.chdir(old_dir)
|
571
|
+
end
|
572
|
+
|
573
|
+
return value
|
574
|
+
end
|
575
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
class RailsInstaller
|
2
|
+
|
3
|
+
# Parent class for command-line subcommand plugins for the installer. Each
|
4
|
+
# subclass must implement the +command+ class method and should provide help
|
5
|
+
# text using the +help+ method. Example (from Typo):
|
6
|
+
#
|
7
|
+
# class SweepCache < RailsInstaller::Command
|
8
|
+
# help "Sweep Typo's cache"
|
9
|
+
#
|
10
|
+
# def self.command(installer, *args)
|
11
|
+
# installer.sweep_cache
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This implements a +sweep_cache+ command that Typo users can access by
|
16
|
+
# running 'typo sweep_cache /some/path'.
|
17
|
+
#
|
18
|
+
# Subcommands that need arguments should use both 'help' and 'flag_help',
|
19
|
+
# and then use the +args+ parameter to find their arguments. For example,
|
20
|
+
# the +install+ subcommand looks like this:
|
21
|
+
#
|
22
|
+
# class Install < RailsInstaller::Command
|
23
|
+
# help "Install or upgrade APPNAME in PATH."
|
24
|
+
# flag_help "[VERSION] [KEY=VALUE]..."
|
25
|
+
#
|
26
|
+
# def self.command(installer, *args)
|
27
|
+
# version = nil
|
28
|
+
# args.each do |arg|
|
29
|
+
# ...
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class Command
|
35
|
+
@@command_map = {}
|
36
|
+
|
37
|
+
# The +command+ method implements this sub-command. It's called by the
|
38
|
+
# command-line parser when the user asks for this sub-command.
|
39
|
+
def self.command(installer, *args)
|
40
|
+
raise "Not Implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
# +flag_help+ sets the help text for any arguments that this sub-command
|
44
|
+
# accepts. It defaults to ''.
|
45
|
+
def self.flag_help(text)
|
46
|
+
@flag_help = text
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the flag help text.
|
50
|
+
def self.flag_help_text
|
51
|
+
@flag_help || ''
|
52
|
+
end
|
53
|
+
|
54
|
+
# +help+ sets the help text for this subcommand.
|
55
|
+
def self.help(text)
|
56
|
+
@help = text
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return the help text.
|
60
|
+
def self.help_text
|
61
|
+
@help || ''
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.inherited(sub)
|
65
|
+
name = sub.to_s.gsub(/^.*::/,'').gsub(/([A-Z])/) do |match|
|
66
|
+
"_#{match.downcase}"
|
67
|
+
end.gsub(/^_/,'')
|
68
|
+
|
69
|
+
@@command_map[name] = sub
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.commands
|
73
|
+
@@command_map
|
74
|
+
end
|
75
|
+
|
76
|
+
# The +install+ command installs the application into a specific path.
|
77
|
+
# Optionally, the user can request a specific version to install. If
|
78
|
+
# the version string is 'cwd', then the current directory is used as a
|
79
|
+
# template; otherwise it looks for the specified version number in the
|
80
|
+
# local Gems repository.
|
81
|
+
class Install < RailsInstaller::Command
|
82
|
+
help "Install or upgrade APPNAME in PATH."
|
83
|
+
flag_help "[VERSION] [KEY=VALUE]..."
|
84
|
+
|
85
|
+
def self.command(installer, *args)
|
86
|
+
version = nil
|
87
|
+
args.each do |arg|
|
88
|
+
if(arg =~ /^([^=]+)=(.*)$/)
|
89
|
+
installer.config[$1.to_s] = $2.to_s
|
90
|
+
else
|
91
|
+
version = arg
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
installer.install(version)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# The +config+ command controls the installation's config
|
100
|
+
# parameters. Running 'installer config /some/path' will show
|
101
|
+
# all of the config parameters for the installation in /some/path.
|
102
|
+
# You can set params with 'key=value', or clear them with 'key='.
|
103
|
+
class Config < RailsInstaller::Command
|
104
|
+
help "Read or set a configuration variable"
|
105
|
+
flag_help '[KEY=VALUE]...'
|
106
|
+
|
107
|
+
def self.command(installer, *args)
|
108
|
+
if args.size == 0
|
109
|
+
installer.config.keys.sort.each do |k|
|
110
|
+
puts "#{k}=#{installer.config[k]}"
|
111
|
+
end
|
112
|
+
else
|
113
|
+
args.each do |arg|
|
114
|
+
if(arg=~/^([^=]+)=(.*)$/)
|
115
|
+
if $2.to_s.empty?
|
116
|
+
installer.config.delete($1.to_s)
|
117
|
+
else
|
118
|
+
installer.config[$1.to_s]=$2.to_s
|
119
|
+
end
|
120
|
+
else
|
121
|
+
puts installer.config[arg]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
installer.save
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# The +start+ command starts a web server in the background for the
|
130
|
+
# specified installation, if applicable.
|
131
|
+
class Start < RailsInstaller::Command
|
132
|
+
help "Start the web server in the background"
|
133
|
+
|
134
|
+
def self.command(installer, *args)
|
135
|
+
installer.start
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# The +run+ command starts a web server in the foreground.
|
140
|
+
class Run < RailsInstaller::Command
|
141
|
+
help "Start the web server in the foreground"
|
142
|
+
|
143
|
+
def self.command(installer, *args)
|
144
|
+
installer.start(true)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# The +restart+ command stops and restarts the web server.
|
149
|
+
class Restart < RailsInstaller::Command
|
150
|
+
help "Stop and restart the web server."
|
151
|
+
|
152
|
+
def self.command(installer, *args)
|
153
|
+
installer.stop
|
154
|
+
installer.start
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# The +stop+ command shuts down the web server.
|
159
|
+
class Stop < RailsInstaller::Command
|
160
|
+
help "Stop the web server"
|
161
|
+
|
162
|
+
def self.command(installer, *args)
|
163
|
+
installer.stop
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# The +backup+ command backs the database up into 'db/backups'.
|
168
|
+
class Backup < RailsInstaller::Command
|
169
|
+
help "Back up the database"
|
170
|
+
|
171
|
+
def self.command(installer, *args)
|
172
|
+
installer.backup_database
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# The +restore+ command restores a backup.
|
177
|
+
class Restore < RailsInstaller::Command
|
178
|
+
help "Restore a database backup"
|
179
|
+
flag_help 'BACKUP_FILENAME'
|
180
|
+
|
181
|
+
def self.command(installer, *args)
|
182
|
+
installer.restore_database(args.first)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
class RailsInstaller
|
4
|
+
|
5
|
+
# Parent class for database plugins for the installer. To create a new
|
6
|
+
# database handler, subclass this class and define a +yml+ class and
|
7
|
+
# optionally a +create_database+ method.
|
8
|
+
class Database
|
9
|
+
@@db_map = {}
|
10
|
+
|
11
|
+
# Connect to the database (using the 'database.yml' generated by the +yml+
|
12
|
+
# method). Returns true if the database already exists, and false if the
|
13
|
+
# database doesn't exist yet.
|
14
|
+
def self.connect(installer)
|
15
|
+
ActiveRecord::Base.establish_connection(
|
16
|
+
YAML.load(yml(installer))['production'])
|
17
|
+
begin
|
18
|
+
tables = ActiveRecord::Base.connection.tables
|
19
|
+
if tables.size > 0
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
rescue
|
23
|
+
# okay
|
24
|
+
end
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Back up the database. This is fully DB and schema agnostic. It
|
29
|
+
# serializes all tables to a single YAML file.
|
30
|
+
def self.backup(installer)
|
31
|
+
STDERR.puts "** backup **"
|
32
|
+
return unless connect(installer)
|
33
|
+
|
34
|
+
interesting_tables = ActiveRecord::Base.connection.tables.sort - ['sessions']
|
35
|
+
backup_dir = File.join(installer.install_directory, 'db', 'backup')
|
36
|
+
FileUtils.mkdir_p backup_dir
|
37
|
+
backup_file = File.join(backup_dir, "backup-#{Time.now.strftime('%Y%m%d-%H%M')}.yml")
|
38
|
+
|
39
|
+
installer.message "Backing up to #{backup_file}"
|
40
|
+
|
41
|
+
data = {}
|
42
|
+
interesting_tables.each do |tbl|
|
43
|
+
data[tbl] = ActiveRecord::Base.connection.select_all("select * from #{tbl}")
|
44
|
+
end
|
45
|
+
|
46
|
+
File.open(backup_file,'w') do |file|
|
47
|
+
YAML.dump data, file
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Restore a backup created by +backup+. Deletes all data before
|
52
|
+
# importing.
|
53
|
+
def self.restore(installer, filename)
|
54
|
+
connect(installer)
|
55
|
+
data = YAML.load(File.read(filename))
|
56
|
+
|
57
|
+
installer.message "Restoring data"
|
58
|
+
data.each_key do |table|
|
59
|
+
if table == 'schema_info'
|
60
|
+
ActiveRecord::Base.connection.execute("delete from schema_info")
|
61
|
+
ActiveRecord::Base.connection.execute("insert into schema_info (version) values (#{data[table].first['version']})")
|
62
|
+
else
|
63
|
+
installer.message " Restoring table #{table} (#{data[table].size})"
|
64
|
+
|
65
|
+
# Create a temporary model to talk to the DB
|
66
|
+
eval %Q{
|
67
|
+
class TempClass < ActiveRecord::Base
|
68
|
+
set_table_name '#{table}'
|
69
|
+
reset_column_information
|
70
|
+
end
|
71
|
+
}
|
72
|
+
|
73
|
+
TempClass.delete_all
|
74
|
+
|
75
|
+
data[table].each do |record|
|
76
|
+
r = TempClass.new(record)
|
77
|
+
r.save
|
78
|
+
end
|
79
|
+
|
80
|
+
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
|
81
|
+
ActiveRecord::Base.connection.reset_pk_sequence!(table)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create a 'database.yml' file, using the data from +yml+.
|
88
|
+
def self.database_yml(installer)
|
89
|
+
yml_file = File.join(installer.install_directory,'config','database.yml')
|
90
|
+
return if File.exists? yml_file
|
91
|
+
|
92
|
+
File.open(yml_file,'w') do |f|
|
93
|
+
f.write(yml(installer))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create the database, including schema creation. This should be generic
|
98
|
+
# enough that database-specific drivers don't need to override it.
|
99
|
+
#
|
100
|
+
# It calls +create_database+ to actually build a new DB from scratch if
|
101
|
+
# needed.
|
102
|
+
def self.create(installer)
|
103
|
+
installer.message "Checking database"
|
104
|
+
if connect(installer)
|
105
|
+
installer.message "Database exists, preparing for upgrade"
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
installer.message "Creating initial database"
|
110
|
+
|
111
|
+
create_database(installer)
|
112
|
+
|
113
|
+
schema_file = File.join(installer.install_directory,'db',"schema.#{installer.config['database']}.sql")
|
114
|
+
schema = File.read(schema_file)
|
115
|
+
|
116
|
+
# Remove comments and extra blank lines
|
117
|
+
schema = schema.split(/\n/).map{|l| l.gsub(/^--.*/,'')}.select{|l| !(l=~/^$/)}.join("\n")
|
118
|
+
|
119
|
+
schema.split(/;\n/).each do |command|
|
120
|
+
ActiveRecord::Base.connection.execute(command)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Create a new database from scratch. Some DBs, like SQLite, don't need
|
125
|
+
# this. Others will need to override this and call the DB's "create new
|
126
|
+
# database" command.
|
127
|
+
def self.create_database(installer)
|
128
|
+
# nothing
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.inherited(sub)
|
132
|
+
name = sub.to_s.gsub(/^.*::/,'').gsub(/([A-Z])/) do |match|
|
133
|
+
"_#{match.downcase}"
|
134
|
+
end.gsub(/^_/,'')
|
135
|
+
|
136
|
+
@@db_map[name] = sub
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.dbs
|
140
|
+
@@db_map
|
141
|
+
end
|
142
|
+
|
143
|
+
# The driver for SQLite 3. This is pretty minimal, as all we need is a
|
144
|
+
# +yml+ class to provide a basic 'database.yml'.
|
145
|
+
class Sqlite < RailsInstaller::Database
|
146
|
+
# The name of the sqlite database file
|
147
|
+
def self.db_file(installer)
|
148
|
+
File.join(installer.install_directory,'db','database.sqlite')
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.yml(installer)
|
152
|
+
%q{
|
153
|
+
login: &login
|
154
|
+
adapter: sqlite3
|
155
|
+
database: db/database.sqlite
|
156
|
+
|
157
|
+
development:
|
158
|
+
<<: *login
|
159
|
+
|
160
|
+
production:
|
161
|
+
<<: *login
|
162
|
+
|
163
|
+
test:
|
164
|
+
database: ":memory:"
|
165
|
+
<<: *login
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# A PostgreSQL driver. This is a bit more work then the SQLite driver, as
|
171
|
+
# Postgres needs to talk to its server. So it takes a number of config
|
172
|
+
# variables:
|
173
|
+
#
|
174
|
+
# * db_host
|
175
|
+
# * db_name
|
176
|
+
# * db_user
|
177
|
+
# * db_password
|
178
|
+
#
|
179
|
+
# It will call +createdb+ to set up the db all on its own.
|
180
|
+
class Postgresql < RailsInstaller::Database
|
181
|
+
def self.db_host(installer)
|
182
|
+
installer.config['db_host'] || 'localhost'
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.db_user(installer)
|
186
|
+
installer.config['db_user'] || ENV['USER'] || installer.app_name
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.db_name(installer)
|
190
|
+
installer.config['db_name'] || installer.app_name
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.yml(installer)
|
194
|
+
%Q{
|
195
|
+
login: &login
|
196
|
+
adapter: postgresql
|
197
|
+
host: #{db_host installer}
|
198
|
+
username: #{db_user installer}
|
199
|
+
password: #{installer.config['db_password']}
|
200
|
+
database: #{db_name installer}
|
201
|
+
|
202
|
+
development:
|
203
|
+
<<: *login
|
204
|
+
|
205
|
+
production:
|
206
|
+
<<: *login
|
207
|
+
|
208
|
+
test:
|
209
|
+
database: #{db_name installer}-test
|
210
|
+
<<: *login
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
# Create a PostgreSQL database.
|
215
|
+
def self.create_database(installer)
|
216
|
+
installer.message "Creating PostgreSQL database"
|
217
|
+
system("createdb -U #{db_user installer} #{db_name installer}")
|
218
|
+
system("createdb -U #{db_user installer} #{db_name installer}-test")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class RailsInstaller
|
2
|
+
|
3
|
+
# Parent class for webserver plugins for the installer. To create a new
|
4
|
+
# webserver handler, subclass this class and define a 'start' and 'stop'
|
5
|
+
# class method.
|
6
|
+
class WebServer
|
7
|
+
@@server_map = {}
|
8
|
+
|
9
|
+
# Start the server
|
10
|
+
def self.start(installer, foreground)
|
11
|
+
raise "Not Implemented"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Stop the server
|
15
|
+
def self.stop(installer, foreground)
|
16
|
+
raise "Not Implemented"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.inherited(sub)
|
20
|
+
name = sub.to_s.gsub(/^.*::/,'').gsub(/([A-Z])/) do |match|
|
21
|
+
"_#{match.downcase}"
|
22
|
+
end.gsub(/^_/,'')
|
23
|
+
|
24
|
+
@@server_map[name] = sub
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.servers
|
28
|
+
@@server_map
|
29
|
+
end
|
30
|
+
|
31
|
+
# A web server plugin for Mongrel (http://mongrel.rubyforge.org).
|
32
|
+
class Mongrel < RailsInstaller::WebServer
|
33
|
+
def self.start(installer, foreground)
|
34
|
+
args = {}
|
35
|
+
args['-p'] = installer.config['port-number']
|
36
|
+
args['-a'] = installer.config['bind-address']
|
37
|
+
args['-e'] = installer.config['rails-environment']
|
38
|
+
args['-d'] = foreground
|
39
|
+
args['-P'] = pid_file(installer)
|
40
|
+
args['--prefix'] = installer.config['url-prefix']
|
41
|
+
|
42
|
+
# Remove keys with nil values
|
43
|
+
args.delete_if {|k,v| v==nil}
|
44
|
+
|
45
|
+
args_array = args.to_a.flatten.map {|e| e.to_s}
|
46
|
+
args_array = ['mongrel_rails', 'start', installer.install_directory] + args_array
|
47
|
+
installer.message "Starting #{installer.app_name.capitalize} on port #{installer.config['port-number']}"
|
48
|
+
in_directory installer.install_directory do
|
49
|
+
system(args_array.join(' '))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.stop(installer)
|
54
|
+
args = {}
|
55
|
+
args['-P'] = pid_file(installer)
|
56
|
+
|
57
|
+
args_array = args.to_a.flatten.map {|e| e.to_s}
|
58
|
+
args_array = ['mongrel_rails', 'stop', installer.install_directory] + args_array
|
59
|
+
installer.message "Stopping #{installer.app_name.capitalize}"
|
60
|
+
in_directory installer.install_directory do
|
61
|
+
system(args_array.join(' '))
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.pid_file(installer)
|
67
|
+
File.join(installer.install_directory,'tmp','pid.txt')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# A web server driver for MongrelCluster.
|
72
|
+
class MongrelCluster < RailsInstaller::WebServer
|
73
|
+
def self.start(installer, foreground)
|
74
|
+
args = {}
|
75
|
+
args['-p'] = installer.config['port-number']
|
76
|
+
args['-a'] = installer.config['bind-address']
|
77
|
+
args['-e'] = installer.config['rails-environment']
|
78
|
+
args['-N'] = installer.config['threads']
|
79
|
+
args['--prefix'] = installer.config['url-prefix']
|
80
|
+
|
81
|
+
# Remove keys with nil values
|
82
|
+
args.delete_if {|k,v| v==nil}
|
83
|
+
|
84
|
+
args_array = args.to_a.flatten.map {|e| e.to_s}
|
85
|
+
args_array = ['mongrel_rails', 'cluster::configure'] + args_array
|
86
|
+
installer.message "Configuring mongrel_cluster for #{installer.app_name.capitalize}"
|
87
|
+
in_directory installer.install_directory do
|
88
|
+
system(args_array.join(' '))
|
89
|
+
end
|
90
|
+
installer.message "Starting #{installer.app_name.capitalize} on port #{installer.config['port-number']}"
|
91
|
+
in_directory installer.install_directory do
|
92
|
+
system('mongrel_rails cluster::start')
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.stop(installer)
|
98
|
+
installer.message "Stopping #{installer.app_name.capitalize}"
|
99
|
+
in_directory installer.install_directory do
|
100
|
+
system('mongrel_rails cluster::stop')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Do-nothing webserver class. Used when the installer doesn't control the
|
106
|
+
# web server, like FastCGI.
|
107
|
+
class External < RailsInstaller::WebServer
|
108
|
+
def self.start(installer, foreground)
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.stop(installer)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: rails-app-installer
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2006-07-28
|
8
|
+
summary: An installer for Rails apps
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: scott@sigkill.org
|
12
|
+
homepage: http://scottstuff.net
|
13
|
+
rubyforge_project:
|
14
|
+
description: ''
|
15
|
+
autorequire: rails-installer
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
authors:
|
28
|
+
- Scott Laird
|
29
|
+
files:
|
30
|
+
- lib/apache13.conf.example.template
|
31
|
+
- lib/apache20.conf.example.template
|
32
|
+
- lib/lighttpd.conf.example.template
|
33
|
+
- lib/rails-installer
|
34
|
+
- lib/rails-installer.rb
|
35
|
+
- examples/installer
|
36
|
+
- examples/rails_installer_defaults.yml
|
37
|
+
- examples/typo
|
38
|
+
- README
|
39
|
+
- lib/rails-installer/commands.rb
|
40
|
+
- lib/rails-installer/databases.rb
|
41
|
+
- lib/rails-installer/web-servers.rb
|
42
|
+
test_files: []
|
43
|
+
rdoc_options:
|
44
|
+
- "--main"
|
45
|
+
- RailsInstaller
|
46
|
+
extra_rdoc_files:
|
47
|
+
- README
|
48
|
+
- lib/rails-installer/commands.rb
|
49
|
+
- lib/rails-installer/databases.rb
|
50
|
+
- lib/rails-installer/web-servers.rb
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
requirements: []
|
54
|
+
dependencies: []
|