| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  | # typed: true # rubocop:todo Sorbet/StrictSigil | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Wrapper for a formula to handle service-related stuff like parsing and | 
					
						
							|  |  |  | # generating the service/plist files. | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  | module Homebrew | 
					
						
							|  |  |  |   module Services | 
					
						
							|  |  |  |     class FormulaWrapper | 
					
						
							|  |  |  |       # Access the `Formula` instance. | 
					
						
							|  |  |  |       sig { returns(Formula) } | 
					
						
							|  |  |  |       attr_reader :formula | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Create a new `Service` instance from either a path or label. | 
					
						
							|  |  |  |       sig { params(path_or_label: T.any(Pathname, String)).returns(T.nilable(FormulaWrapper)) } | 
					
						
							|  |  |  |       def self.from(path_or_label) | 
					
						
							|  |  |  |         return unless path_or_label =~ path_or_label_regex | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         begin | 
					
						
							|  |  |  |           new(Formulary.factory(T.must(Regexp.last_match(1)))) | 
					
						
							|  |  |  |         rescue | 
					
						
							|  |  |  |           nil | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Initialize a new `Service` instance with supplied formula. | 
					
						
							|  |  |  |       sig { params(formula: Formula).void } | 
					
						
							|  |  |  |       def initialize(formula) | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @formula = formula | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Delegate access to `formula.name`. | 
					
						
							|  |  |  |       sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       def name | 
					
						
							|  |  |  |         @name ||= formula.name | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Delegate access to `formula.service?`. | 
					
						
							|  |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def service? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @service ||= @formula.service? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Delegate access to `formula.service.timed?`. | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       # TODO: this should either be T::Boolean or renamed to `timed` | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T.nilable(T::Boolean)) } | 
					
						
							|  |  |  |       def timed? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @timed ||= (load_service.timed? if service?) | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       # Delegate access to `formula.service.keep_alive?`. | 
					
						
							|  |  |  |       # TODO: this should either be T::Boolean or renamed to `keep_alive` | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T.nilable(T::Boolean)) } | 
					
						
							|  |  |  |       def keep_alive? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @keep_alive ||= (load_service.keep_alive? if service?) | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       # service_name delegates with formula.plist_name or formula.service_name | 
					
						
							|  |  |  |       # for systemd (e.g., `homebrew.<formula>`). | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T.nilable(String)) } | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       def service_name | 
					
						
							|  |  |  |         @service_name ||= if System.launchctl? | 
					
						
							|  |  |  |           formula.plist_name | 
					
						
							|  |  |  |         elsif System.systemctl? | 
					
						
							|  |  |  |           formula.service_name | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # service_file delegates with formula.launchd_service_path or formula.systemd_service_path for systemd. | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       def service_file | 
					
						
							|  |  |  |         @service_file ||= if System.launchctl? | 
					
						
							|  |  |  |           formula.launchd_service_path | 
					
						
							|  |  |  |         elsif System.systemctl? | 
					
						
							|  |  |  |           formula.systemd_service_path | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Whether the service should be launched at startup | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def service_startup? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @service_startup ||= if service? | 
					
						
							|  |  |  |           load_service.requires_root? | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           false | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Path to destination service directory. If run as root, it's `boot_path`, else `user_path`. | 
					
						
							|  |  |  |       def dest_dir | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         System.root? ? System.boot_path : System.user_path | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Path to destination service. If run as root, it's in `boot_path`, else `user_path`. | 
					
						
							|  |  |  |       def dest | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         dest_dir + service_file.basename | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Returns `true` if any version of the formula is installed. | 
					
						
							|  |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def installed? | 
					
						
							|  |  |  |         formula.any_version_installed? | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Returns `true` if the plist file exists. | 
					
						
							|  |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def plist? | 
					
						
							|  |  |  |         return false unless installed? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         return true if service_file.file? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         return false unless formula.opt_prefix.exist? | 
					
						
							|  |  |  |         return true if Keg.for(formula.opt_prefix).plist_installed? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         false | 
					
						
							|  |  |  |       rescue NotAKegError | 
					
						
							|  |  |  |         false | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-01 06:29:37 +01:00
										 |  |  |       sig { void } | 
					
						
							|  |  |  |       def reset_cache! | 
					
						
							|  |  |  |         @status_output_success_type = nil | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Returns `true` if the service is loaded, else false. | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       # TODO: this should either be T::Boolean or renamed to `loaded` | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { params(cached: T::Boolean).returns(T.nilable(T::Boolean)) } | 
					
						
							|  |  |  |       def loaded?(cached: false) | 
					
						
							|  |  |  |         if System.launchctl? | 
					
						
							| 
									
										
										
										
											2025-05-01 06:29:37 +01:00
										 |  |  |           reset_cache! unless cached | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           _, status_success, = status_output_success_type | 
					
						
							|  |  |  |           status_success | 
					
						
							|  |  |  |         elsif System.systemctl? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |           System::Systemctl.quiet_run("status", service_file.basename) | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Returns `true` if service is present (e.g. .plist is present in boot or user service path), else `false` | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       # Accepts `type` with values `:root` for boot path or `:user` for user path. | 
					
						
							|  |  |  |       sig { params(type: T.nilable(Symbol)).returns(T::Boolean) } | 
					
						
							|  |  |  |       def service_file_present?(type: nil) | 
					
						
							|  |  |  |         case type | 
					
						
							|  |  |  |         when :root | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           boot_path_service_file_present? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         when :user | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           user_path_service_file_present? | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           boot_path_service_file_present? || user_path_service_file_present? | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def owner | 
					
						
							|  |  |  |         if System.launchctl? && dest.exist? | 
					
						
							|  |  |  |           # read the username from the plist file | 
					
						
							|  |  |  |           plist = begin | 
					
						
							|  |  |  |             Plist.parse_xml(dest.read, marshal: false) | 
					
						
							|  |  |  |           rescue | 
					
						
							|  |  |  |             nil | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           plist_username = plist["UserName"] if plist | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return plist_username if plist_username.present? | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         return "root" if boot_path_service_file_present? | 
					
						
							|  |  |  |         return System.user if user_path_service_file_present? | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         nil | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def pid? | 
					
						
							| 
									
										
										
										
											2025-03-15 19:42:33 +00:00
										 |  |  |         pid.present? && pid.positive? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def error? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         return false if pid? | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-15 19:42:33 +00:00
										 |  |  |         exit_code.present? && !exit_code.zero? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def unknown_status? | 
					
						
							|  |  |  |         status_output.blank? && !pid? | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Get current PID of daemon process from status output. | 
					
						
							|  |  |  |       def pid | 
					
						
							|  |  |  |         status_output, _, status_type = status_output_success_type | 
					
						
							|  |  |  |         Regexp.last_match(1).to_i if status_output =~ pid_regex(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # Get current exit code of daemon process from status output. | 
					
						
							|  |  |  |       def exit_code | 
					
						
							|  |  |  |         status_output, _, status_type = status_output_success_type | 
					
						
							|  |  |  |         Regexp.last_match(1).to_i if status_output =~ exit_code_regex(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-07 14:05:39 +01:00
										 |  |  |       def loaded_file | 
					
						
							|  |  |  |         status_output, _, status_type = status_output_success_type | 
					
						
							|  |  |  |         Regexp.last_match(1) if status_output =~ loaded_file_regex(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       sig { returns(T::Hash[Symbol, T.anything]) } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def to_hash | 
					
						
							|  |  |  |         hash = { | 
					
						
							|  |  |  |           name:, | 
					
						
							|  |  |  |           service_name:, | 
					
						
							|  |  |  |           running:      pid?, | 
					
						
							|  |  |  |           loaded:       loaded?(cached: true), | 
					
						
							|  |  |  |           schedulable:  timed?, | 
					
						
							|  |  |  |           pid:, | 
					
						
							|  |  |  |           exit_code:, | 
					
						
							|  |  |  |           user:         owner, | 
					
						
							|  |  |  |           status:       status_symbol, | 
					
						
							|  |  |  |           file:         service_file_present? ? dest : service_file, | 
					
						
							| 
									
										
										
										
											2025-03-20 07:16:02 +00:00
										 |  |  |           registered:   service_file_present?, | 
					
						
							| 
									
										
										
										
											2025-04-07 14:05:39 +01:00
										 |  |  |           loaded_file:, | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return hash unless service? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         service = load_service | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         return hash if service.command.blank? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         hash[:command] = service.manual_command | 
					
						
							|  |  |  |         hash[:working_dir] = service.working_dir | 
					
						
							|  |  |  |         hash[:root_dir] = service.root_dir | 
					
						
							|  |  |  |         hash[:log_path] = service.log_path | 
					
						
							|  |  |  |         hash[:error_log_path] = service.error_log_path | 
					
						
							|  |  |  |         hash[:interval] = service.interval | 
					
						
							|  |  |  |         hash[:cron] = service.cron | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         hash | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       private | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       # The purpose of this function is to lazy load the Homebrew::Service class | 
					
						
							|  |  |  |       # and avoid nameclashes with the current Service module. | 
					
						
							|  |  |  |       # It should be used instead of calling formula.service directly. | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |       sig { returns(Homebrew::Service) } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def load_service | 
					
						
							|  |  |  |         require "formula" | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         formula.service | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def status_output_success_type | 
					
						
							|  |  |  |         @status_output_success_type ||= if System.launchctl? | 
					
						
							| 
									
										
										
										
											2025-04-03 10:01:45 +01:00
										 |  |  |           cmd = [System.launchctl.to_s, "print", "#{System.domain_target}/#{service_name}"] | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |           output = Utils.popen_read(*cmd).chomp | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           if $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? | 
					
						
							|  |  |  |             success = true | 
					
						
							| 
									
										
										
										
											2025-04-03 10:01:45 +01:00
										 |  |  |             type = :launchctl_print | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           else | 
					
						
							| 
									
										
										
										
											2025-04-03 10:01:45 +01:00
										 |  |  |             cmd = [System.launchctl.to_s, "list", service_name] | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |             output = Utils.popen_read(*cmd).chomp | 
					
						
							|  |  |  |             success = $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? | 
					
						
							| 
									
										
										
										
											2025-04-03 10:01:45 +01:00
										 |  |  |             type = :launchctl_list | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2025-04-03 10:01:45 +01:00
										 |  |  |           odebug cmd.join(" "), output | 
					
						
							|  |  |  |           [output, success, type] | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         elsif System.systemctl? | 
					
						
							|  |  |  |           cmd = ["status", service_name] | 
					
						
							|  |  |  |           output = System::Systemctl.popen_read(*cmd).chomp | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |           success = $CHILD_STATUS.present? && $CHILD_STATUS.success? && output.present? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           odebug [System::Systemctl.executable, System::Systemctl.scope, *cmd].join(" "), output | 
					
						
							|  |  |  |           [output, success, :systemctl] | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |       def status_output | 
					
						
							|  |  |  |         status_output, = status_output_success_type | 
					
						
							|  |  |  |         status_output | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(Symbol) } | 
					
						
							|  |  |  |       def status_symbol | 
					
						
							|  |  |  |         if pid? | 
					
						
							|  |  |  |           :started | 
					
						
							|  |  |  |         elsif !loaded?(cached: true) | 
					
						
							|  |  |  |           :none | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         elsif exit_code.present? && exit_code.zero? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           if timed? | 
					
						
							|  |  |  |             :scheduled | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             :stopped | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         elsif error? | 
					
						
							|  |  |  |           :error | 
					
						
							|  |  |  |         elsif unknown_status? | 
					
						
							|  |  |  |           :unknown | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |         else | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           :other | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def exit_code_regex(status_type) | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @exit_code_regex ||= { | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           launchctl_list:  /"LastExitStatus"\ =\ ([0-9]*);/, | 
					
						
							|  |  |  |           launchctl_print: /last exit code = ([0-9]+)/, | 
					
						
							|  |  |  |           systemctl:       /\(code=exited, status=([0-9]*)\)|\(dead\)/, | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         @exit_code_regex.fetch(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       def pid_regex(status_type) | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         @pid_regex ||= { | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |           launchctl_list:  /"PID"\ =\ ([0-9]*);/, | 
					
						
							|  |  |  |           launchctl_print: /pid = ([0-9]+)/, | 
					
						
							|  |  |  |           systemctl:       /Main PID: ([0-9]*) \((?!code=)/, | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |         @pid_regex.fetch(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-07 14:05:39 +01:00
										 |  |  |       def loaded_file_regex(status_type) | 
					
						
							|  |  |  |         @loaded_file_regex ||= { | 
					
						
							|  |  |  |           launchctl_list:  //, # not available | 
					
						
							|  |  |  |           launchctl_print: /path = (.*)/, | 
					
						
							|  |  |  |           systemctl:       /Loaded: .*? \((.*);/, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         @loaded_file_regex.fetch(status_type) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def boot_path_service_file_present? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         boot_path = System.boot_path | 
					
						
							|  |  |  |         return false if boot_path.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         (boot_path + service_file.basename).exist? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(T::Boolean) } | 
					
						
							|  |  |  |       def user_path_service_file_present? | 
					
						
							| 
									
										
										
										
											2025-03-14 16:53:07 +00:00
										 |  |  |         user_path = System.user_path | 
					
						
							|  |  |  |         return false if user_path.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         (user_path + service_file.basename).exist? | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-14 04:35:30 +00:00
										 |  |  |       sig { returns(Regexp) } | 
					
						
							|  |  |  |       private_class_method def self.path_or_label_regex | 
					
						
							|  |  |  |         /homebrew(?>\.mxcl)?\.([\w+-.@]+)(\.plist|\.service)?\z/ | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2025-02-26 13:26:37 +01:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |