Class: RailsInstaller
- Inherits:
-
Object
- Object
- RailsInstaller
- Includes:
- FileUtils
- Defined in:
- lib/rails-installer.rb,
lib/rails-installer/commands.rb,
lib/rails-installer/databases.rb,
lib/rails-installer/web-servers.rb
Overview
An installer for Rails applications.
The Rails Application Installer is designed to make it easy for end-users to install open-source Rails apps with a minimum amount of effort. When built properly, all the user needs to do is run:
$ gem install my_app
$ my_app install /some/path
To use this installer, you’ll need to create a small driver program (the ‘my_app’ program from above). Here’s a minimal example:
#!/usr/bin/env ruby
require 'rubygems'
require 'rails-installer'
class AppInstaller < RailsInstaller
application_name 'my_shiny_metal_app'
support_location 'our shiny website'
rails_version '1.1.4'
end
# Installer program
directory = ARGV[1]
app = AppInstaller.new(directory)
app. = Proc.new do |msg|
STDERR.puts " #{msg}"
end
app.execute_command(*ARGV)
Place this in your application’s gem/ directory, and then add it to your .gem using the ‘executables’ gemspec option. See the examples/ directory for more complex examples.
Defined Under Namespace
Classes: Command, Database, InstallFailed, WebServer
Constant Summary collapse
- @@rails_version =
nil
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#install_directory ⇒ Object
Returns the value of attribute install_directory.
-
#message_proc ⇒ Object
Returns the value of attribute message_proc.
-
#source_directory ⇒ Object
Returns the value of attribute source_directory.
Class Method Summary collapse
-
.application_name(name) ⇒ Object
The application name.
-
.rails_version(svn_tag) ⇒ Object
Which Rails version this app needs.
-
.support_location(location) ⇒ Object
The support location.
Instance Method Summary collapse
-
#app_name ⇒ Object
The application name, as set by
application_name
. -
#backup_config_file ⇒ Object
The path to the config file that comes with the GEM.
-
#backup_database ⇒ Object
Backup the database.
-
#config_file ⇒ Object
The path to the installed config file.
-
#copy_files ⇒ Object
Copy files from the source directory to the target directory.
-
#copy_gem(spec, destination) ⇒ Object
Copy a specific gem’s contents.
-
#copy_one_file(filename) ⇒ Object
Copy one file from source_directory to install_directory, creating directories as needed.
-
#create_default_config_files ⇒ Object
Create all default config files.
-
#create_default_database_yml ⇒ Object
Create the default database.yml.
-
#create_directories ⇒ Object
Create required directories, like tmp.
-
#create_initial_database ⇒ Object
Create the initial SQLite database.
-
#display_help(error = nil) ⇒ Object
Display help.
-
#execute_command(*args) ⇒ Object
Execute a command-line command.
-
#expand_template_files ⇒ Object
Expand configuration template files.
-
#find_source_directory(gem_name, version) ⇒ Object
Locate the source directory for a specific Version.
-
#fix_permissions ⇒ Object
Clean up file and directory permissions.
-
#freeze_rails ⇒ Object
Freeze to a specific version of Rails gems.
-
#get_schema_version ⇒ Object
Get the current schema version.
-
#hash_diff(a, b) ⇒ Object
Compute the different between two hashes.
-
#initialize(install_directory) ⇒ RailsInstaller
constructor
A new instance of RailsInstaller.
-
#install(version = nil) ⇒ Object
Install Application.
-
#install_post_hook ⇒ Object
Another install hook;
install_post_hook
runs after the final migration. -
#install_pre_hook ⇒ Object
The easy way to add steps to the installation process.
-
#install_sequence ⇒ Object
The default install sequence.
-
#message(string) ⇒ Object
Display a status message.
-
#migrate ⇒ Object
Migrate the database.
-
#pre_migrate_database ⇒ Object
Pre-migrate the database.
-
#read_yml(filename) ⇒ Object
Load a yaml file.
-
#restore_database(filename) ⇒ Object
Restore the database.
-
#run_rails_tests ⇒ Object
Run Rails tests.
-
#save ⇒ Object
Save config settings.
-
#set_initial_port_number ⇒ Object
Pick a default port number.
-
#sha1_hash_directory_tree(directory, prefix = '', hash = {}) ⇒ Object
Find all files in a directory tree and return a Hash containing sha1 hashes of all files.
-
#start(foreground = false) ⇒ Object
Start application in the background.
-
#stop ⇒ Object
Stop application.
-
#system_silently(command) ⇒ Object
Call
system
, ignoring all output. -
#write_yml(filename, object) ⇒ Object
Save a yaml file.
Constructor Details
#initialize(install_directory) ⇒ RailsInstaller
Returns a new instance of RailsInstaller.
78 79 80 81 82 83 84 85 86 |
# File 'lib/rails-installer.rb', line 78 def initialize(install_directory) # use an absolute path, not a relative path. if install_directory @install_directory = File.(install_directory) end @config = read_yml(config_file) rescue nil @config ||= Hash.new end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
49 50 51 |
# File 'lib/rails-installer.rb', line 49 def config @config end |
#install_directory ⇒ Object
Returns the value of attribute install_directory.
49 50 51 |
# File 'lib/rails-installer.rb', line 49 def install_directory @install_directory end |
#message_proc ⇒ Object
Returns the value of attribute message_proc.
50 51 52 |
# File 'lib/rails-installer.rb', line 50 def @message_proc end |
#source_directory ⇒ Object
Returns the value of attribute source_directory.
49 50 51 |
# File 'lib/rails-installer.rb', line 49 def source_directory @source_directory end |
Class Method Details
.application_name(name) ⇒ Object
The application name. Set this in your derived class.
57 58 59 |
# File 'lib/rails-installer.rb', line 57 def self.application_name(name) @@app_name = name end |
.rails_version(svn_tag) ⇒ Object
Which Rails version this app needs. This version of Rails will be frozen into vendor/rails/
69 70 71 |
# File 'lib/rails-installer.rb', line 69 def self.rails_version(svn_tag) @@rails_version = svn_tag end |
.support_location(location) ⇒ Object
The support location. This is displayed to the user at the end of the install process.
63 64 65 |
# File 'lib/rails-installer.rb', line 63 def self.support_location(location) @@support_location = location end |
Instance Method Details
#app_name ⇒ Object
The application name, as set by application_name
.
74 75 76 |
# File 'lib/rails-installer.rb', line 74 def app_name @@app_name end |
#backup_config_file ⇒ Object
The path to the config file that comes with the GEM
389 390 391 |
# File 'lib/rails-installer.rb', line 389 def backup_config_file File.join(source_directory,'installer','rails_installer_defaults.yml') end |
#backup_database ⇒ Object
Backup the database
171 172 173 174 |
# File 'lib/rails-installer.rb', line 171 def backup_database db_class = RailsInstaller::Database.dbs[config['database']] db_class.backup(self) end |
#config_file ⇒ Object
The path to the installed config file
384 385 386 |
# File 'lib/rails-installer.rb', line 384 def config_file File.join(install_directory,'installer','rails_installer.yml') end |
#copy_files ⇒ Object
Copy files from the source directory to the target directory.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/rails-installer.rb', line 185 def copy_files "Checking for existing #{@@app_name.capitalize} install in #{install_directory}" files_yml = File.join(install_directory,'installer','files.yml') old_files = read_yml(files_yml) rescue Hash.new "Reading files from #{source_directory}" new_files = sha1_hash_directory_tree(source_directory) new_files.delete('/config/database.yml') # Never copy this. # Next, we compare the original install hash to the current hash. For each # entry: # # - in new_file but not in old_files: copy # - in old files but not in new_files: delete # - in both, but hash different: copy # - in both, hash same: don't copy # # We really should add a third hash (existing_files) and compare against that # so we don't overwrite changed files. added, changed, deleted, same = hash_diff(old_files, new_files) if added.size > 0 "Copying #{added.size} new files into #{install_directory}" added.keys.sort.each do |file| " copying #{file}" copy_one_file(file) end end if changed.size > 0 "Updating #{changed.size} files in #{install_directory}" changed.keys.sort.each do |file| " updating #{file}" copy_one_file(file) end end if deleted.size > 0 "Deleting #{deleted.size} files from #{install_directory}" deleted.keys.sort.each do |file| " deleting #{file}" rm(File.join(install_directory,file)) rescue nil end end write_yml(files_yml,new_files) end |
#copy_gem(spec, destination) ⇒ Object
Copy a specific gem’s contents.
332 333 334 335 |
# File 'lib/rails-installer.rb', line 332 def copy_gem(spec, destination) ("copying #{spec.name} #{spec.version} to #{destination}") cp_r("#{spec.full_gem_path}/.",destination) end |
#copy_one_file(filename) ⇒ Object
Copy one file from source_directory to install_directory, creating directories as needed.
237 238 239 240 241 242 243 244 |
# File 'lib/rails-installer.rb', line 237 def copy_one_file(filename) source_name = File.join(source_directory,filename) install_name = File.join(install_directory,filename) dir_name = File.dirname(install_name) mkdir_p(dir_name) cp(source_name,install_name,:preserve => true) end |
#create_default_config_files ⇒ Object
Create all default config files
338 339 340 |
# File 'lib/rails-installer.rb', line 338 def create_default_config_files create_default_database_yml end |
#create_default_database_yml ⇒ Object
Create the default database.yml
343 344 345 346 |
# File 'lib/rails-installer.rb', line 343 def create_default_database_yml db_class = RailsInstaller::Database.dbs[config['database']] db_class.database_yml(self) end |
#create_directories ⇒ Object
Create required directories, like tmp
359 360 361 362 363 364 365 366 367 368 |
# File 'lib/rails-installer.rb', line 359 def create_directories mkdir_p(File.join(install_directory,'tmp','cache')) chmod(0755, File.join(install_directory,'tmp','cache')) mkdir_p(File.join(install_directory,'tmp','session')) mkdir_p(File.join(install_directory,'tmp','sockets')) mkdir_p(File.join(install_directory,'log')) File.open(File.join(install_directory,'log','development.log'),'w') File.open(File.join(install_directory,'log','production.log'),'w') File.open(File.join(install_directory,'log','testing.log'),'w') end |
#create_initial_database ⇒ Object
Create the initial SQLite database
371 372 373 374 375 376 |
# File 'lib/rails-installer.rb', line 371 def create_initial_database db_class = RailsInstaller::Database.dbs[config['database']] in_directory(install_directory) do db_class.create(self) end end |
#display_help(error = nil) ⇒ Object
Display help.
546 547 548 549 550 551 552 553 554 555 556 557 558 |
# File 'lib/rails-installer.rb', line 546 def display_help(error=nil) STDERR.puts error if error commands = Command.commands.keys.sort commands.each do |cmd| cmd_class = Command.commands[cmd] flag_help = cmd_class.flag_help_text.gsub(/APPNAME/,app_name) help = cmd_class.help_text.gsub(/APPNAME/,app_name) STDERR.puts " #{app_name} #{cmd} DIRECTORY #{flag_help}" STDERR.puts " #{help}" end end |
#execute_command(*args) ⇒ Object
Execute a command-line command
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'lib/rails-installer.rb', line 529 def execute_command(*args) if args.size < 2 display_help exit(1) end command_class = Command.commands[args.first] if command_class command_class.command(self,*(args[2..-1])) else display_help exit(1) end end |
#expand_template_files ⇒ Object
Expand configuration template files.
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/rails-installer.rb', line 510 def rails_host = config['bind-address'] || `hostname`.chomp rails_port = config['port-number'].to_s rails_url = "http://#{rails_host}:#{rails_port}" Dir[File.join(install_directory,'installer','*.template')].each do |template_file| output_file = template_file.gsub(/\.template/,'') next if File.exists?(output_file) # don't overwrite files "expanding #{File.basename(output_file)} template" text = File.read(template_file).gsub(/\$RAILS_URL/,rails_url).gsub(/\$RAILS_HOST/,rails_host).gsub(/\$RAILS_PORT/,rails_port) File.open(output_file,'w') do |f| f.write text end end end |
#find_source_directory(gem_name, version) ⇒ Object
Locate the source directory for a specific Version
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 |
# File 'lib/rails-installer.rb', line 481 def find_source_directory(gem_name, version) if version == 'cwd' return Dir.pwd elsif version version_array = ["= #{version}"] else version_array = ["> 0.0.0"] end specs = Gem.source_index.find_name(gem_name,version_array) unless specs.to_a.size > 0 raise InstallFailed, "Can't locate version #{version}!" end specs.last.full_gem_path end |
#fix_permissions ⇒ Object
Clean up file and directory permissions.
349 350 351 352 353 354 355 356 |
# File 'lib/rails-installer.rb', line 349 def unless RUBY_PLATFORM =~ /mswin32/ "Making scripts executable" chmod 0555, File.join(install_directory,'public','dispatch.fcgi') chmod 0555, File.join(install_directory,'public','dispatch.cgi') chmod 0555, Dir[File.join(install_directory,'script','*')] end end |
#freeze_rails ⇒ Object
Freeze to a specific version of Rails gems.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/rails-installer.rb', line 283 def freeze_rails return unless @@rails_version version_file = File.join(install_directory,'vendor','rails-version') vendor_rails = File.join(install_directory,'vendor','rails') old_version = File.read(version_file).chomp rescue nil if @@rails_version == old_version return elsif old_version != nil rm_rf(vendor_rails) end mkdir_p(vendor_rails) package_map = { 'rails' => File.join(vendor_rails,'railties'), 'actionmailer' => File.join(vendor_rails,'actionmailer'), 'actionpack' => File.join(vendor_rails,'actionpack'), 'actionwebservice' => File.join(vendor_rails,'actionwebservice'), 'activerecord' => File.join(vendor_rails,'activerecord'), 'activesupport' => File.join(vendor_rails,'activesupport'), } specs = Gem.source_index.find_name('rails',["= #{@@rails_version}"]) unless specs.to_a.size > 0 raise InstallFailed, "Can't locate Rails #{@@rails_version}!" end copy_gem(specs.first, package_map[specs.first.name]) specs.first.dependencies.each do |dep| next unless package_map[dep.name] dep_spec = Gem.source_index.find_name(dep.name,[dep.version_requirements.to_s]) if dep_spec.size == 0 raise InstallFailed, "Can't locate dependency #{dep.name} #{dep.version_requirements.to_s}" end copy_gem(dep_spec.first, package_map[dep.name]) end File.open(version_file,'w') do |f| f.puts @@rails_version end end |
#get_schema_version ⇒ Object
Get the current schema version
379 380 381 |
# File 'lib/rails-installer.rb', line 379 def get_schema_version File.read(File.join(install_directory,'db','schema_version')).to_i rescue 0 end |
#hash_diff(a, b) ⇒ Object
Compute the different between two hashes. Returns four hashes, one contains the keys that are in ‘b’ but not in ‘a’ (added entries), the next contains keys that are in ‘a’ and ‘b’, but have different values (changed). The third contains keys that are in ‘b’ but not ‘a’ (added). The final hash contains items that are the same in each.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/rails-installer.rb', line 251 def hash_diff(a, b) added = {} changed = {} deleted = {} same = {} seen = {} a.each_key do |k| seen[k] = true if b.has_key? k if b[k] == a[k] same[k] = true else changed[k] = true end else deleted[k] = true end end b.each_key do |k| unless seen[k] added[k] = true end end [added, changed, deleted, same] end |
#install(version = nil) ⇒ Object
Install Application
98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/rails-installer.rb', line 98 def install(version=nil) @source_directory = find_source_directory(@@app_name,version) # Merge default configuration settings @config = read_yml(backup_config_file).merge(config) install_sequence '' "#{@@app_name.capitalize} is now running on http://#{`hostname`.chomp}:#{config['port-number']}" "Use '#{@@app_name} start #{install_directory}' to restart after boot." "Look in installer/*.conf.example to see how to integrate with your web server." end |
#install_post_hook ⇒ Object
Another install hook; install_post_hook
runs after the final migration.
145 146 |
# File 'lib/rails-installer.rb', line 145 def install_post_hook end |
#install_pre_hook ⇒ Object
The easy way to add steps to the installation process. install_pre_hook
runs right after the DB is backed up and right before the first migration attempt.
141 142 |
# File 'lib/rails-installer.rb', line 141 def install_pre_hook end |
#install_sequence ⇒ Object
The default install sequence. Override this if you need to add extra steps to the installer.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/rails-installer.rb', line 114 def install_sequence stop backup_database install_pre_hook pre_migrate_database copy_files freeze_rails create_default_config_files create_directories create_initial_database set_initial_port_number migrate install_post_hook save run_rails_tests start end |
#message(string) ⇒ Object
Display a status message
89 90 91 92 93 94 95 |
# File 'lib/rails-installer.rb', line 89 def (string) if .call(string) else STDERR.puts string end end |
#migrate ⇒ Object
Migrate the database
420 421 422 423 424 425 426 427 428 |
# File 'lib/rails-installer.rb', line 420 def migrate "Migrating #{@@app_name.capitalize}'s database to newest release" in_directory install_directory do unless system("rake -s migrate") raise InstallFailed, "Migration failed" end end end |
#pre_migrate_database ⇒ Object
Pre-migrate the database. This checks to see if we’re downgrading to an earlier version of our app, and runs ‘rake migrate VERSION=…’ to downgrade the database.
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/rails-installer.rb', line 401 def pre_migrate_database old_schema_version = get_schema_version new_schema_version = File.read(File.join(source_directory,'db','schema_version')).to_i return unless old_schema_version > 0 # Are we downgrading? if old_schema_version > new_schema_version "Downgrading schema from #{old_schema_version} to #{new_schema_version}" in_directory install_directory do unless system("rake -s migrate VERSION=#{new_schema_version}") raise InstallFailed, "Downgrade migrating from #{old_schema_version} to #{new_schema_version} failed." end end end end |
#read_yml(filename) ⇒ Object
Load a yaml file
469 470 471 |
# File 'lib/rails-installer.rb', line 469 def read_yml(filename) YAML.load(File.read(filename)) end |
#restore_database(filename) ⇒ Object
Restore the database
177 178 179 180 181 182 |
# File 'lib/rails-installer.rb', line 177 def restore_database(filename) db_class = RailsInstaller::Database.dbs[config['database']] in_directory install_directory do db_class.restore(self, filename) end end |
#run_rails_tests ⇒ Object
Run Rails tests. This helps verify that we have a clean install with all dependencies. This cuts down on a lot of bug reports.
432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/rails-installer.rb', line 432 def run_rails_tests "Running tests. This may take a minute or two" in_directory install_directory do if system_silently("rake -s test") "All tests pass. Congratulations." else "***** Tests failed *****" "** Please run 'rake test' by hand in your install directory." "** Report problems to #{@@support_location}." "***** Tests failed *****" end end end |
#save ⇒ Object
Save config settings
464 465 466 |
# File 'lib/rails-installer.rb', line 464 def save write_yml(config_file,@config) end |
#set_initial_port_number ⇒ Object
Pick a default port number
394 395 396 |
# File 'lib/rails-installer.rb', line 394 def set_initial_port_number config['port-number'] ||= (rand(1000)+4000) end |
#sha1_hash_directory_tree(directory, prefix = '', hash = {}) ⇒ Object
Find all files in a directory tree and return a Hash containing sha1 hashes of all files.
449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/rails-installer.rb', line 449 def sha1_hash_directory_tree(directory, prefix='', hash={}) Dir.entries(directory).each do |file| next if file =~ /^\./ pathname = File.join(directory,file) if File.directory?(pathname) sha1_hash_directory_tree(pathname, File.join(prefix,file), hash) else hash[File.join(prefix,file)] = Digest::SHA1.hexdigest(File.read(pathname)) end end hash end |
#start(foreground = false) ⇒ Object
Start application in the background
149 150 151 152 153 154 155 156 |
# File 'lib/rails-installer.rb', line 149 def start(foreground = false) server_class = RailsInstaller::WebServer.servers[config['web-server']] if not server_class "** warning: web-server #{config['web-server']} unknown. Use 'web-server=external' to disable." end server_class.start(self,foreground) end |
#stop ⇒ Object
Stop application
159 160 161 162 163 164 165 166 167 168 |
# File 'lib/rails-installer.rb', line 159 def stop return unless File.directory?(install_directory) server_class = RailsInstaller::WebServer.servers[config['web-server']] if not server_class "** warning: web-server #{config['web-server']} unknown. Use 'web-server=external' to disable." end server_class.stop(self) end |
#system_silently(command) ⇒ Object
Call system
, ignoring all output.
499 500 501 502 503 504 505 506 507 |
# File 'lib/rails-installer.rb', line 499 def system_silently(command) if RUBY_PLATFORM =~ /mswin32/ null = 'NUL:' else null = '/dev/null' end system("#{command} > #{null} 2> #{null}") end |
#write_yml(filename, object) ⇒ Object
Save a yaml file.
474 475 476 477 478 |
# File 'lib/rails-installer.rb', line 474 def write_yml(filename,object) File.open(filename,'w') do |f| f.write(YAML.dump(object)) end end |