require 'fileutils' require 'rainbow' %w{apachify directory logging master modules mpm_prefork performance permissions rewrites ssl}.each do |file| require "apache/#{file}" end module Apache # The core class of Apache Config Generator. # # Configuration is built by calling either build or build_if: # # Ex: Build a config file regardless of current environment: # # Apache::Config.build('my-config.conf') do # document_root '/my/document/root' # ... # end # # Ex: Build a config file only if you're in the development environment: # # Apache::Config.build_if('my-config.conf', :development) do # document_root '/my/document/root' # ... # end # # By default, methods called within the block are NerdCapsed to match the Apache config directive format: # # document_root #=> DocumentRoot # options #=> Options # allow_override #=> AllowOverride # # Parameters passed into the methods are quoted if they're Strings or Integers, and not quoted if they're Symbols: # # document_root '/my/document/root' #=> DocumentRoot "/my/document/root" # document_root '/my/document/root'.to_sym #=> DocumentRoot /my/document/root # accept_path_info :off #=> AcceptPathInfo off # # Suffixing the method name with an exclamation point turns off quoting for all parameters: # # document_root! '/my/document/root' #=> DocumentRoot /my/document/root # # Block-level directives work the same as the top-level build method: # # directory '/my/site' do # allow_from_all # satisfy :any # end # # Directives that require a regular expression take a Regexp: # # location_match %r{^/my/site} do # set_env 'this_is_my_site', 'yes' # end class Config class << self attr_accessor :line_indent, :rotate_logs_path include Apache::Master include Apache::Permissions include Apache::Directories include Apache::Logging include Apache::Performance include Apache::Rewrites include Apache::MPM include Apache::SSL # Build the provided configuration only if the current environment matches one of the conditions def build_if(target, *conditions, &block) build(target, &block) if environment_ok?(*conditions) end # Build the provided configuration def build_and_return(&block) reset! self.instance_eval(&block) @config end def build_and_return_if(*conditions, &block) build_and_return(&block) if environment_ok?(*conditions) end def environment_ok?(*environments) return true if APACHE_ENV == true environments.include?(APACHE_ENV) end def build(target, &block) config = build_and_return(&block) FileUtils.mkdir_p File.split(target).first File.open(target, 'w') { |file| file.puts generate_config_file(config) * "\n" } config end # If included in a configuration, will not generate the symlink in the Rake task def disable_symlink! @is_disabled = true end def generate_config_file(config) output = [ "# Generated by apache-config-generator #{Time.now.to_s}", config ] output.unshift('# disabled') if @is_disabled output.flatten end # Reset the current settings def reset! @config = [] @line_indent = 0 @is_disabled = false end # Indent the string by the current @line_indent level def indent(string_or_array) case string_or_array when Array string_or_array.collect { |line| indent(line) } else " " * (@line_indent * 2) + string_or_array.to_s end end # Add the string to the current config def <<(string) @config << indent(string) end # Append the array to the current config def +(other) @config += other end # Get the config def to_a @config end # Handle options that aren't specially handled # # Method names are NerdCapsed and paramters are quoted, unless the method ends with ! def method_missing(method, *args) method_name = method.to_s if method_name[-1..-1] == "!" method = method_name[0..-2].to_sym else args.quoteize! end self << [ method.apachify, *args ].compact * ' ' end # Handle creating block methods # # Methods created this way are: # * virtual_host # * location # * files def block_methods(*methods) methods.each do |method| self.class.class_eval <<-EOT def #{method}(*name, &block) blockify("#{method}".apachify, name, &block) end EOT end end # If the given module is loaded, process the directives within. # # The provided module name is converted into Apache module name format: # if_module(:php5) do #=> def if_module(mod, &block) blockify('if_module'.apachify, "#{mod}_module".to_sym, &block) end # Create a directory block, checking to see if the source directory exists. def directory(dir, &block) directory? dir blockify('directory'.apachify, dir, &block) end # Create a LocationMatch block with the provided Regexp: # location_match %r{^/my/location/[a-z0-9]+\.html} do #=> def location_match(regexp, &block) blockify('location_match'.apachify, regexp.source, &block) end # Create a FilesMatch block with the provied Regexp: # files_match %r{\.html$} do #=> FilesMatch "\.html$"> def files_match(regexp, &block) blockify('files_match'.apachify, regexp.source, &block) end # Only execute the provided block if APACHE_ENV matches one of the provided enviroment symbols: # if_environment(:production) do def if_environment(*env, &block) self.instance_eval(&block) if env.include?(APACHE_ENV) end # Handle the blockification of a provided block def blockify(tag_name, name, &block) self + [ '', "<#{[ tag_name, name.blockify ].compact * ' '}>" ] @line_indent += 1 self.instance_eval(&block) @line_indent -= 1 self + [ "", '' ] end def blank_line! self << "" end # Build a string that invokes Apache's rotatelogs command def rotatelogs(path, time) begin time = time.to_i rescue raise "Time should be an integer: #{path} #{time}" end "|#{@rotate_logs_path} #{path} #{time}" end def warn_msg(from, color = :red) "[warn::#{from}]".foreground(color) end private def writable?(path) puts " #{warn_msg('writable?')} #{path.foreground(:yellow)} may not be writable!" if !File.directory? File.split(path).first end def directory?(path) puts " #{warn_msg('directory?')} #{path.foreground(:yellow)} does not exist!" if !File.directory? path end def exist?(path) puts " #{warn_msg('exist?')} #{path.foreground(:yellow)} does not exist!" if !File.exist?(path) end end block_methods :virtual_host, :location, :files end end