/ launcher / launcher.rb
launcher.rb
  1  # Drop all environment variables so they don't interfere with our war file gem loading.
  2  java.lang.System.set_property("aspace.launcher.base", ENV['ASPACE_LAUNCHER_BASE'])
  3  
  4  require 'jruby'
  5  require 'aspace_gems'
  6  ASpaceGems.setup
  7  
  8  require_relative 'launcher_init'
  9  require 'asutils'
 10  require 'fileutils'
 11  require 'securerandom'
 12  require 'uri'
 13  require 'ashttp'
 14  
 15  $server_prepare_hooks = []
 16  
 17  
 18  # Add a new hook that will be called as a Jetty server is prepared.
 19  # Each hook will be called with:
 20  #
 21  #   hook.call(server, port, [{:war => '/path/to/app.war', :path => '/uri'}, ...])
 22  #
 23  # Nothing uses this feature by default, but you could use it for performing
 24  # further configuration on the Jetty server object (such as sizing thread pools)
 25  # by adding a hook from code in your ASPACE_LAUNCHER_BASE/launcher_rc.rb file.
 26                                                                        #
 27  def add_server_prepare_hook(callback)
 28    $server_prepare_hooks << callback
 29  end
 30  
 31  
 32  def start_server(port, *webapps)
 33    server = org.eclipse.jetty.server.Server.new
 34  
 35    configuration = org.eclipse.jetty.server.HttpConfiguration.new
 36    configuration.request_header_size = AppConfig[:jetty_request_buffer_size_bytes] || 64 * 1024
 37    configuration.response_header_size = AppConfig[:jetty_response_buffer_size_bytes] || 64 * 1024
 38    configuration.send_date_header
 39    configuration.send_server_version
 40    configuration.send_xpowered_by
 41  
 42    http = org.eclipse.jetty.server.HttpConnectionFactory.new(configuration)
 43    connector = org.eclipse.jetty.server.ServerConnector.new(server, http)
 44    connector.port = port
 45    server.add_connector(connector)
 46  
 47    contexts = webapps.map do |webapp|
 48      if webapp[:war]
 49        context = org.eclipse.jetty.webapp.WebAppContext.new
 50        context.server = server
 51        context.context_path = webapp[:path]
 52        context.war = webapp[:war]
 53        context.class_loader = org.eclipse.jetty.webapp.WebAppClassLoader.new(JRuby.runtime.jruby_class_loader, context)
 54  
 55        context
 56      elsif webapp[:static_dirs]
 57        handlers = org.eclipse.jetty.server.handler.HandlerList.new
 58  
 59        Array(webapp[:static_dirs]).each do |static_dir|
 60          handler = org.eclipse.jetty.server.handler.ResourceHandler.new
 61          handler.set_resource_base(static_dir)
 62  
 63          handlers.add_handler(handler)
 64        end
 65  
 66        ctx = org.eclipse.jetty.server.handler.ContextHandler.new(webapp[:path])
 67        ctx.set_handler(handlers)
 68  
 69        ctx
 70      else
 71        raise "Unrecognised webapp definition: #{webapp.inspect}"
 72      end
 73    end
 74  
 75    # this establishes a shutdown port on jetty. use the context xkcd so there is
 76    # little change of this overlapping on a server
 77    # posting to /xkcd/shutdown?password= will stop that jetty instance
 78    if AppConfig[:use_jetty_shutdown_handler]
 79      shtctx = org.eclipse.jetty.server.handler.ContextHandler.new(AppConfig[:jetty_shutdown_path])
 80      shtctx.set_handler(org.eclipse.jetty.server.handler.ShutdownHandler.new(server, generate_secret_for("jetty_shutdown")))
 81      contexts << shtctx
 82    end
 83    collection = org.eclipse.jetty.server.handler.ContextHandlerCollection.new
 84    collection.handlers = contexts
 85    server.handler = collection
 86  
 87    $server_prepare_hooks.each do |hook|
 88      hook.call(server, port, webapps)
 89    end
 90  
 91    server.start
 92  
 93    server
 94  end
 95  
 96  
 97  def generate_secret_for(secret)
 98    file = File.join(AppConfig[:data_directory], "#{secret}.dat")
 99  
100    if !File.exist?(file)
101      File.write(file, SecureRandom.hex)
102  
103      puts "****"
104      puts "**** INFO: Generated a secret key for AppConfig[:#{secret}]"
105      puts "****       and stored it in #{file}."
106      puts "****"
107      unless secret == "shutdown"
108        puts "**** If you're running ArchivesSpace in a clustered setup, you will"
109        puts "**** need to make sure that all instances share the same value for this"
110        puts "**** setting.  You can do that by setting a value for AppConfig[:#{secret}]"
111        puts "**** in your config.rb file."
112        puts "****"
113      end
114      puts ""
115    end
116  
117    File.read(file)
118  end
119  
120  
121  def main
122    java.lang.System.set_property("org.eclipse.jetty.webapp.LEVEL", "WARN")
123    java.lang.System.set_property("org.eclipse.jetty.server.handler.LEVEL", "WARN")
124  
125  
126    String tempdir = File.join(AppConfig[:data_directory], "tmp")
127  
128    FileUtils.mkdir_p(tempdir)
129  
130    java.lang.System.set_property("java.io.tmpdir", tempdir)
131  
132    [:search_user_secret, :public_user_secret, :staff_user_secret].each do |property|
133      if !AppConfig.has_key?(property)
134        java.lang.System.set_property("aspace.config.#{property}", SecureRandom.hex)
135      end
136    end
137  
138    cookie_secrets = [:frontend_cookie_secret, :public_cookie_secret].each do |secret|
139      if !AppConfig.has_key?("#{secret}".intern)
140        java.lang.System.set_property("aspace.config.#{secret}",
141                                      generate_secret_for(secret))
142      end
143    end
144  
145    servers = []
146  
147    begin
148      aspace_base = java.lang.System.get_property("ASPACE_LAUNCHER_BASE")
149      servers << start_server(URI(AppConfig[:backend_url]).port, {:war => File.join(aspace_base, 'wars', 'backend.war'), :path => '/'}) if AppConfig[:enable_backend]
150  
151      servers << start_server(URI(AppConfig[:indexer_url]).port,
152                   {:war => File.join(aspace_base, 'wars', 'indexer.war'), :path => '/aspace-indexer'}) if AppConfig[:enable_indexer]
153  
154      servers << start_server(URI(AppConfig[:frontend_url]).port,
155                   {:war => File.join(aspace_base, 'wars', 'frontend.war'), :path => '/'},
156                   {:static_dirs => ASUtils.find_local_directories("frontend/assets"),
157                         :path => "#{AppConfig[:frontend_proxy_prefix]}assets"}) if AppConfig[:enable_frontend]
158  
159      servers << start_server(URI(AppConfig[:public_url]).port,
160                   {:war => File.join(aspace_base, 'wars', 'public.war'), :path => '/'},
161                   {:static_dirs => ASUtils.find_local_directories("public/assets"),
162                          :path => "#{AppConfig[:public_proxy_prefix]}assets"}) if AppConfig[:enable_public]
163  
164      servers << start_server(URI(AppConfig[:docs_url]).port,
165                   {:static_dirs => File.join(aspace_base, "docs", "_site"), :path => '/archivesspace'}) if AppConfig[:enable_docs]
166  
167      servers << start_server(URI(AppConfig[:oai_url]).port,
168                   {:war => File.join(aspace_base, 'wars', 'oai.war'), :path => '/'}) if AppConfig[:enable_oai]
169  
170  
171    rescue
172      # If anything fails on startup, dump a diagnostic file.
173      ASUtils.dump_diagnostics($!)
174    end
175  
176    puts <<~EOF
177      ************************************************************
178        Welcome to ArchivesSpace!
179        You can now point your browser to #{AppConfig[:frontend_url]}
180      ************************************************************
181    EOF
182  
183    servers.first&.join
184  end
185  
186  
187  def stop_server(uri)
188    puts "Stopping : #{uri.to_s}"
189  
190    shutdown_uri = uri.clone
191    shutdown_uri.path = "/xkcd/shutdown"
192    response = ASHTTP.post_form(shutdown_uri, 'token' => generate_secret_for("jetty_shutdown"))
193  
194    if response.code != 404
195      #now we check to see if indeed the server has shutdown. should return an
196      #connection error.
197      ASHTTP.get(uri)
198  
199      puts "Jetty Shutdown error on #{uri.to_s}"
200      puts "Shutdown returned: #{response.code}"
201      puts "#{response.body}"
202    else
203      puts "Jetty Shutdown handler does not exist"
204    end
205  rescue Errno::ECONNREFUSED, SocketError, EOFError => se
206    # A little odd, but when jetty shutdowns it just shutsdown and no response is
207    # sent. Some jrubys handle this differently, but most raise either a
208    # Connection, socket, or a rbuff_fill execption. When this happens, we can
209    # assume the shutdown has worked.
210    puts "#{uri.to_s} not running"
211  rescue Exception => e
212    # Server is possibly still running so overall shutdown may fail
213    puts "Unexpected shutdown error"
214    puts e.inspect
215  end
216  
217  
218  def stop
219    if AppConfig[:use_jetty_shutdown_handler]
220      stop_server(URI(AppConfig[:frontend_url])) if AppConfig[:enable_frontend]
221      stop_server(URI(AppConfig[:public_url])) if AppConfig[:enable_public]
222      stop_server(URI(AppConfig[:docs_url])) if AppConfig[:enable_docs]
223      stop_server(URI(AppConfig[:indexer_url])) if AppConfig[:enable_indexer]
224      stop_server(URI(AppConfig[:backend_url])) if AppConfig[:enable_backend]
225      pid_file = File.join(AppConfig[:data_directory], ".archivesspace.pid" )
226      FileUtils.rm(pid_file) if File.exist?(pid_file)
227      java.lang.System.exit(0)
228    else
229      puts "****"
230      puts "AppConfig[:use_jetty_shutdown_handler] has not been set. "
231      puts "To use this shutdown command, you must update the config.rb file."
232      puts "****"
233    end
234  rescue => e
235    puts "There has been an error issueing shutdown to Jetty."
236    puts e.inspect
237  end
238  
239  launcher_rc = File.join(java.lang.System.get_property("ASPACE_LAUNCHER_BASE"), "launcher_rc.rb")
240  
241  if java.lang.System.get_property("ASPACE_LAUNCHER_BASE") && File.exist?(launcher_rc)
242    load File.absolute_path(launcher_rc)
243  end
244  
245  
246  if ARGV[0] == 'stop'
247    stop
248  else
249    main
250  end