#!/usr/bin/env ruby # Copyright (c) 2009, Keith Salisbury (www.globalkeith.com) # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # Neither the name of the original author nor the names of contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 'yaml' require 'erb' require 'fileutils' require 'subtrac/version' require 'subtrac/commands' require 'subtrac/client' require 'subtrac/project' require 'subtrac/apache' require 'subtrac/svn' require 'subtrac/trac' require 'subtrac/core_ext' module Subtrac SUBTRAC_ROOT = "#{File.dirname(__FILE__)}/" unless defined?(SUBTRAC_ROOT) SUBTRAC_ENV = (ENV['SUBTRAC_ENV'] || 'development').dup unless defined?(SUBTRAC_ENV) USER_CONFIG_FILE = 'config/user.yml' class << self # User configured options def server_name @server_name ||= @APP_CONFIG[:server_name] end def server_name=(name) @APP_CONFIG[:server_name] = @server_name = name end def server_hostname @server_hostname ||= @APP_CONFIG[:server_hostname] end def server_hostname=(name) @APP_CONFIG[:server_hostname] = @server_hostname = name end def default_client @default_client ||= @APP_CONFIG[:default_client] end def default_client=(name) @APP_CONFIG[:default_client] = @default_client = name end def default_project @default_project ||= @APP_CONFIG[:default_project] end def default_project=(name) puts "Updating default_project to #{name}" @APP_CONFIG[:default_project] = @default_project = name end def default_project_type @default_project_type ||= @APP_CONFIG[:default_project_type] end def default_project_type=(name) puts "Updating default_project_type to #{name}" @APP_CONFIG[:default_project_type] = @default_project_type = name end # Filesystem directories def root Pathname.new(SUBTRAC_ROOT) if defined?(SUBTRAC_ROOT) end def public_path @public_path ||= self.root ? File.join(self.root, "public") : "public" end def public_path=(path) @public_path = path end def subtrac_path @subtrac_path ||= self.root ? File.join(self.root, "subtrac") : "subtrac" end def install_dir @install_dir ||= File.expand_path(@APP_CONFIG[:installation_dir]) end def install_dir=(name) puts "Setting new install_dir variable to #{name}" @APP_CONFIG[:installation_dir] = @install_dir = name end def apache_conf_dir @apache_conf_dir ||= @APP_CONFIG[:apache_conf_dir] end def apache_conf_dir=(name) @APP_CONFIG[:apache_conf_dir] = @apache_conf_dir = name end def docs_dir @docs_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:docs]) end def svn_dir @svn_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:svn]) end def trac_dir @trac_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:trac]) end def temp_dir @temp_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:temp]) end def log_dir @log_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:log]) end def locations_dir @locations_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:locations]) end # Urls def svn_url @svn_url ||= @APP_CONFIG[:urls][:svn] end def trac_permissions @trac_permissions ||= @APP_CONFIG[:trac][:permissions] end # LDAP def enable_ldap @enable_ldap ||= @APP_CONFIG[:ldap][:enable] end def enable_ldap=(value) @APP_CONFIG[:ldap][:enable] = @enable_ldap = value end def ldap_bind_dn @ldap_bind_dn ||= @APP_CONFIG[:ldap][:bind_dn] end def ldap_bind_dn=(value) @APP_CONFIG[:ldap][:bind_dn] = @ldap_bind_dn = value end def ldap_bind_password @ldap_bind_password ||= @APP_CONFIG[:ldap][:bind_password] end def ldap_bind_password=(value) @APP_CONFIG[:ldap][:bind_password] = @ldap_bind_password = value end def ldap_bind_host @ldap_bind_host ||= @APP_CONFIG[:ldap][:bind_host] end def ldap_bind_host=(value) @APP_CONFIG[:ldap][:bind_host] = @ldap_bind_password = value end end # Loads the configuration YML file def self.load_config # TODO: We need to refactor this code so it will load the default configuration only if one has not been created puts "\n==== Loading configuration file ====" # load configuration file file_path = File.join(subtrac_path, USER_CONFIG_FILE) file_path = File.join(subtrac_path,"/config/config.yml") if not File.exists?(file_path) puts "Attempting to read config file: #{file_path}" begin yamlFile = YAML.load(File.read(file_path)) rescue Exception => e raise StandardError, "Config #{file_path} could not be loaded." end if yamlFile if yamlFile[SUBTRAC_ENV] @APP_CONFIG = yamlFile[SUBTRAC_ENV] else raise StandardError, "config/config.yml exists, but doesn't have a configuration for #{SUBTRAC_ENV}." end else raise StandardError, "config/config.yml does not exist." end say("Configuration loaded successfully...") end # Write the changes configuration to disk def self.save_config # save the things that might have changed file_path = File.join(subtrac_path, USER_CONFIG_FILE) user_config = {SUBTRAC_ENV => @APP_CONFIG} open(file_path, 'w') {|f| YAML.dump(user_config, f)} end # Install def self.install(args,options) puts "\n==== Installing development server files ====" if options.defaults then overwrite = options.clean confirm_default_client = true else # check where we are installing confirm_or_replace_value(:install_dir,"install_dir") unless !File.directory?(install_dir) # Ask if the user agrees (yes or no) confirm_clean = agree("Err, it seems there's some stuff in there. You sure you want me to overwrite? [Y/n]") if options.clean overwrite = agree("Doubly sure? I can't undo this....[Y/n]") if confirm_clean end # confirm server confirm_or_replace_value(:server_name,"server_name") # ask for hostname confirm_or_replace_value(:server_hostname,"server_hostname") # default client/project name confirm_or_replace_value(:default_client,"default_client") confirm_or_replace_value(:default_project,"default_project") end say("Ok we're about to install now, these are the options you have chosen: installation directory: #{install_dir} overwrite: #{overwrite} server name: #{server_name} server hostname: #{server_hostname} default client: #{default_client} default project: #{default_project}") confirm = agree("Is this ok? [y/n]") exit 0 if !confirm create_environment_directories(overwrite) #create_virtual_host() apache = Apache.new(binding) apache.create_virtual_host() install_common_files() configure_admin_user() # create default project create_project(default_project,default_client) # store any user preferences for later use save_config() end def self.create_project(project,client) # create default project project = Project.new(project,client,default_project_type) # get these out for binding...we'll tidy up later client = project.client # create the svn repo svn = Svn.new(svn_dir, binding) svn.create_project(project) # create the trac site trac = Trac.new(trac_dir, binding) trac.create_project(project) # create the apache configuration apache.create_project(project) # fix privileges give_apache_privileges() # return the project project end private def self.give_apache_privileges # make sure apache can operate on these files `sudo chown -R www-data:www-data #{install_dir}` end # confirms the current value with the user or accepts a new value if required def self.confirm_or_replace_value(prop, name) method(prop).call # initialise the property current_value = instance_variable_get("@#{prop}") accept_default = agree("The default value for #{prop} @#{prop} is \"#{current_value}\". Is this ok? [y/n]") if !accept_default then answer = ask("What would you like to change it to?") send("#{prop}=", answer) #instance_variable_set(prop, answer) end end def self.install_common_files puts "\n==== Installing common files ====" # TODO: implement a mask for .svn folders # TODO: refactor /common to the app config FileUtils.cp_r(Dir.glob(File.join(subtrac_path, "common/.")),docs_dir) FileUtils.cp_r(Dir.glob(File.join(subtrac_path, "shared/.")),File.create_if_missing(File.join(trac_dir, ".shared"))) end def self.configure_admin_user puts "\n==== Configure admin user ====" # create admin user passwd_file = File.join(install_dir, ".passwd") admin_user = ask("New admin user: ") { |q| q.echo = true } admin_pass = ask("New password: ") { |q| q.echo = "*" } admin_pass_confirm = ask("Re-type new password: ") { |q| q.echo = "*" } if (admin_pass == admin_pass_confirm) then `htpasswd -c -b #{passwd_file} #{admin_user} #{admin_pass}` @APP_CONFIG[:admin_user] = admin_user @APP_CONFIG[:admin_pass] = admin_pass # ensure this guy is added to trac admin group @APP_CONFIG[:trac][:permissions][admin_user] = "admins" else # call the password chooser again end end def self.create_environment_directories(overwrite=false) puts "\n==== Creating new environment directories ====" FileUtils.rm_rf install_dir if overwrite File.create_if_missing install_dir # create the environment directories @APP_CONFIG[:dirs].each do |key, value| dir = File.join(install_dir,value) File.create_if_missing dir end end end