# frozen_string_literal: true # Requirements # ======================================================================= # Stdlib # ----------------------------------------------------------------------- # Deps # ----------------------------------------------------------------------- require 'plist' require 'fileutils' # Project / Package # ----------------------------------------------------------------------- # Refinements # ======================================================================= require 'nrser/refinements/types' using NRSER::Types # Definitions # ======================================================================= # An site is a {Locd::Agent} that the proxy can route HTTP requests to. # # If you send an HTTP request to the proxy with the site's label as the # host, the proxy will start the site if it isn't running and route the # request to it's {#port}. # # Combined with DNSMasq pointing the site's label to the proxy (and using # the port the proxy is running on) this allows you to open the site's # {#url} in the browser (or make a request to it from anything that resolves # DNS properly through DNSMasq... should work fine for APIs and other HTTP # services) and get through to the agent. # class Locd::Agent::Site < Locd::Agent # Constants # ========================================================================== # Address we expect servers to bind to so we can reach them. Provided # during command rendering as `{bind}` so it can be used when running # service commands. # # For the moment at least this seems to need to be `127.0.0.1` 'cause I # think ProxyMachine had trouble connecting to `localhost`, so services # need to bind to it or be reachable at it. # # @return [String] # BIND = '127.0.0.1'.freeze # Attribute / method names that {#to_h} uses. # # @return [Hamster::SortedSet] # TO_H_NAMES = Locd::Agent::TO_H_NAMES.union [:port, :url] # Structs # ============================================================================ class Status < Locd::Agent::Status # @!attribute [r] port # Site backend port. # # @return [Integer] # prop :port, type: t.pos_int end # class Status # Class Methods # ========================================================================== # See if a parsed plist looks like a site. # # @param [Hash] plist # Property list parsed by {Plist.parse_xml}. # # @return [Boolean] # `true` if we think this `plist` belongs to a site. # def self.plist? plist !!( plist.dig( Locd.config[:agent, :config_key], 'port' ) && plist.dig( Locd.config[:agent, :config_key], 'is_system' ) != true ) end # .plist? # @!group Querying # -------------------------------------------------------------------------- # Sorted set of all ports configured for installed {Locd::Agent::Site}. # # Used to determine available ports to allocate when creating new services. # # @return [Hamster::SortedSet] # def self.ports Hamster::SortedSet.new all.values.map( &:port ) end # @!endgroup # @!group Creating Servers # -------------------------------------------------------------------------- # Create the `launchd` property list data for a new {Locd::Agent::Site}, which # has an additional `port:` keyword versus {Locd::Agent.create_plist_data}. # # @param cmd_template (see Locd::Agent.create_plist_data) # @param label (see Locd::Agent.create_plist_data) # @param workdir (see Locd::Agent.create_plist_data) # @param log_path (see Locd::Agent.create_plist_data) # @param keep_alive (see Locd::Agent.create_plist_data) # @param run_at_load (see Locd::Agent.create_plist_data) # # @param [nil | Fixnum | String] port # Port to run the service on. If you don't provide one, one will be # provided for you (see {Locd::Proxy.allocate_port}). # # @param [String] bind # Address that the server should bind to. # # @return (see Locd::Agent.create_plist_data) # def self.create_plist_data cmd_template:, label:, workdir:, log_path: nil, keep_alive: false, run_at_load: false, port: nil, bind: Locd.config[:site, :bind], open_path: '/' # Allocate a port if one was not provided port = if port.nil? Locd::Proxy.allocate_port else # or just normalize to {Fixnum} port.to_i end open_path = "/#{ path }" unless open_path.start_with? '/' super cmd_template: cmd_template, label: label, workdir: workdir, log_path: log_path, keep_alive: keep_alive, run_at_load: run_at_load, # Extras specific to {Locd::Agent::Site}: port: port, bind: bind, open_path: open_path end # .create_plist_data # Instance Methods # ============================================================================ # @!group Instance Methods: Attribute Readers # ---------------------------------------------------------------------------- # # Methods to read proxied and computed attributes. # # @return [Integer] # Port service runs on. def port config['port'] end def open_path config['open_path'] || '/' end # @return [String] # The URL the agent can be reached at through the proxy. def url "http://#{ label }:#{ Locd::Proxy.port }#{ open_path }" end # @!endgroup Instance Methods: Attribute Readers # @!group `launchctl` Interface Instance Methods # ---------------------------------------------------------------------------- # The site agent's status from parsing `launchctl list`. # # Status is read on demand and cached on the instance. # # @param [Boolean] refresh: # When `true`, will re-read from `launchd` (and cache results) # before returning. # # @return [Status] # def status refresh: false Status.new port: port, **super( refresh: refresh ) end # @!endgroup `launchctl` Interface Instance Methods end # class Locd::Launchd