rails-app-installer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +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: []
|