# frozen_string_literal: true# Released under the MIT License.# Copyright, 2024, by Samuel Williams.require'async/service/generic'moduleFalconmoduleService# A controller which mananages several virtual hosts.# Spawns instances of {Proxy} and {Redirect} to handle incoming requests.## A virtual host is an application bound to a specific authority (essentially a hostname). The virtual controller manages multiple hosts and allows a single server to host multiple applications easily.classVirtual<Async::Service::GenericmoduleEnvironment# The service class to use for the virtual host.# @returns [Class]defservice_classVirtualenddefnameservice_class.nameend# All the falcon application configuration paths.# @returns [Array(String)] Paths to the falcon application configuration files.defconfiguration_paths["/srv/http/*/falcon.rb"]enddefconfiguration::Async::Service::Configuration.load(configuration_paths)end# The URI to bind the `HTTPS` -> `falcon host` proxy.defbind_secure"https://[::]:443"end# The URI to bind the `HTTP` -> `HTTPS` redirector.defbind_insecure"http://[::]:80"end# The connection timeout to use for incoming connections.deftimeout10.0end# # The insecure endpoint for connecting to the {Redirect} instance.# def insecure_endpoint(**options)# Async::HTTP::Endpoint.parse(bind_insecure, **options)# end# # The secure endpoint for connecting to the {Proxy} instance.# def secure_endpoint(**options)# Async::HTTP::Endpoint.parse(bind_secure, **options)# end# # An endpoint suitable for connecting to the specified hostname.# def host_endpoint(hostname, **options)# endpoint = secure_endpoint(**options)# url = URI.parse(bind_secure)# url.hostname = hostname# return Async::HTTP::Endpoint.new(url, hostname: endpoint.hostname)# endenddefself.included(target)target.include(Environnment)end# Drop privileges according to the user and group of the specified path.# @parameter path [String] The path to the application directory.defassume_privileges(path)stat=File.stat(path)Process::GID.change_privilege(stat.gid)Process::UID.change_privilege(stat.uid)home=Etc.getpwuid(stat.uid).dirreturn{'HOME'=>home,}end# Spawn an application instance from the specified path.# @parameter path [String] The path to the application directory.# @parameter container [Async::Container::Generic] The container to spawn into.# @parameter options [Options] The options which are passed to `exec`.defspawn(path,container,**options)container.spawn(name: "Falcon Application",restart: true,key: path)do|instance|env=assume_privileges(path)instance.exec(env,"bundle","exec","--keep-file-descriptors",path,ready: false,**options)endend# The path to the falcon executable from this gem.# @returns [String]deffalcon_pathFile.expand_path("../../../bin/falcon",__dir__)end# Setup the container with {Redirect} and {Proxy} child processes.# These processes are gracefully restarted if they are already running.# @parameter container [Async::Container::Generic]defsetup(container)ifproxy=container[:proxy]proxy.kill(:HUP)endifredirect=container[:redirect]redirect.kill(:HUP)endcontainer.reloaddoevaluator=@environment.evaluatorevaluator.configuration_paths.eachdo|path|path=File.expand_path(path)root=File.dirname(path)spawn(path,container,chdir: root)endcontainer.spawn(name: "Falcon Redirector",restart: true,key: :redirect)do|instance|instance.exec(falcon_path,"redirect","--bind",evaluator.bind_insecure,"--timeout",evaluator.timeout.to_s,"--redirect",evaluator.bind_secure,*evaluator.configuration_paths,ready: false)endcontainer.spawn(name: "Falcon Proxy",restart: true,key: :proxy)do|instance|instance.exec(falcon_path,"proxy","--bind",evaluator.bind_secure,"--timeout",evaluator.timeout.to_s,*evaluator.configuration_paths,ready: false)endendendendendend