Class: Homebrew::Service Private

Inherits:
Object show all
Extended by:
Forwardable
Includes:
OnSystem::MacOSAndLinux, Utils::Output::Mixin, Utils::Path
Defined in:
service.rb,
sorbet/rbi/dsl/homebrew/service.rbi

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

The Service class implements the DSL methods used in a formula's service block and stores related instance variables. Most of these methods also return the related instance variable when no argument is provided.

Constant Summary collapse

RUN_TYPE_IMMEDIATE =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:immediate
RUN_TYPE_INTERVAL =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:interval
RUN_TYPE_CRON =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:cron
PROCESS_TYPE_BACKGROUND =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:background
PROCESS_TYPE_STANDARD =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:standard
PROCESS_TYPE_INTERACTIVE =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:interactive
PROCESS_TYPE_ADAPTIVE =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

:adaptive
KEEP_ALIVE_KEYS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

[:always, :successful_exit, :crashed, :path].freeze
NICE_RANGE =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let(-20..19, T::Range[Integer])
SOCKET_STRING_REGEX =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

%r{^(?<type>[a-z]+)://(?<host>.+):(?<port>[0-9]+)$}i
RunParam =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.type_alias { T.nilable(T.any(T::Array[T.any(String, Pathname)], String, Pathname)) }
Sockets =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.type_alias { T::Hash[Symbol, { host: String, port: String, type: String }] }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::Path

formula_installed_prefixes, formula_opt_bin_env, formula_opt_bin_path, install_info, loadable_package_path?, uninstall_info

Methods included from Utils::Path::Helpers

#child_of?, #cp_path_sub, #ensure_child_of!, #ensure_writable, #resolved_path, #resolved_path_exists?, #rmdir_if_possible, #text_executable?

Methods included from Utils::Path::FormulaHelpers

#formula_any_version_installed?, #formula_opt_bin, #formula_opt_include, #formula_opt_lib, #formula_opt_libexec, #formula_opt_prefix

Methods included from Utils::Output::Mixin

#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_cannot_install, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning

Methods included from OnSystem::MacOSAndLinux

included, #on_arch_conditional, #on_macos, #on_system_conditional

Constructor Details

#initialize(formula, &block) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Raises:

  • (TypeError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'service.rb', line 42

def initialize(formula, &block)
  @cron = T.let({}, T::Hash[Symbol, T.any(Integer, String)])
  @environment_variables = T.let({}, T::Hash[Symbol, String])
  @error_log_path = T.let(nil, T.nilable(String))
  @formula = formula
  @input_path = T.let(nil, T.nilable(String))
  @interval = T.let(nil, T.nilable(Integer))
  @keep_alive = T.let({}, T::Hash[Symbol, T.untyped])
  @launch_only_once = T.let(false, T::Boolean)
  @log_path = T.let(nil, T.nilable(String))
  @macos_legacy_timers = T.let(false, T::Boolean)
  @nice = T.let(nil, T.nilable(Integer))
  @plist_name = T.let(default_plist_name, String)
  @plist_name_explicitly_set = T.let(false, T::Boolean)
  @process_type = T.let(nil, T.nilable(Symbol))
  @require_root = T.let(false, T::Boolean)
  @restart_delay = T.let(nil, T.nilable(Integer))
  @throttle_interval = T.let(nil, T.nilable(Integer))
  @root_dir = T.let(nil, T.nilable(String))
  @run = T.let([], T::Array[String])
  @run_at_load = T.let(true, T::Boolean)
  @run_params = T.let(nil, T.any(RunParam, T::Hash[Symbol, RunParam]))
  @run_type = T.let(RUN_TYPE_IMMEDIATE, Symbol)
  @service_name = T.let(default_service_name, String)
  @service_name_explicitly_set = T.let(false, T::Boolean)
  @sockets = T.let({}, Sockets)
  @stop_timeout = T.let(nil, T.nilable(Integer))
  @working_dir = T.let(nil, T.nilable(String))
  instance_eval(&block) if block

  raise TypeError, "Service#nice: require_root true is required for negative nice values" if nice_requires_root?
end

Instance Attribute Details

#plist_nameString (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



39
40
41
# File 'service.rb', line 39

def plist_name
  @plist_name
end

#service_nameString (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



39
40
41
# File 'service.rb', line 39

def service_name
  @service_name
end

Class Method Details

.from_hash(api_hash) ⇒ Hash{Symbol => T.untyped}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Turn the service API hash values back into what is expected by the formula DSL.

Parameters:

Returns:



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
# File 'service.rb', line 814

def self.from_hash(api_hash)
  hash = {}
  hash[:name] = api_hash["name"].transform_keys(&:to_sym) if api_hash.key?("name")

  # The service hash might not have a "run" command if it only documents
  # an existing service file with the "name" command.
  return hash unless api_hash.key?("run")

  hash[:run] =
    case api_hash["run"]
    when String, Pathname
      replace_placeholders(api_hash["run"])
    when Array
      api_hash["run"].map { replace_placeholders(it) }
    when Hash
      api_hash["run"].to_h do |key, elem|
        run_cmd = if elem.is_a?(Array)
          elem.map { replace_placeholders(it) }
        else
          replace_placeholders(elem)
        end

        [key.to_sym, run_cmd]
      end
    else
      raise ArgumentError, "Unexpected run command: #{api_hash["run"]}"
    end

  if api_hash.key?("environment_variables")
    hash[:environment_variables] = api_hash["environment_variables"].to_h do |key, value|
      [key.to_sym, replace_placeholders(value)]
    end
  end

  %w[run_type process_type].each do |key|
    next unless (value = api_hash[key])

    hash[key.to_sym] = value.to_sym
  end

  %w[working_dir root_dir input_path log_path error_log_path].each do |key|
    next unless (value = api_hash[key])

    hash[key.to_sym] = replace_placeholders(value)
  end

  %w[interval cron launch_only_once require_root restart_delay throttle_interval stop_timeout nice
     macos_legacy_timers].each do |key|
    next if (value = api_hash[key]).nil?

    hash[key.to_sym] = value
  end

  %w[sockets keep_alive].each do |key|
    next unless (value = api_hash[key])

    hash[key.to_sym] = if value.is_a?(Hash)
      value.transform_keys(&:to_sym)
    else
      value
    end
  end

  hash
end

.replace_placeholders(string) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Replace API path placeholders with local paths.

Parameters:

Returns:



882
883
884
885
886
887
# File 'service.rb', line 882

def self.replace_placeholders(string)
  string.to_s
        .gsub(HOMEBREW_PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
        .gsub(HOMEBREW_CELLAR_PLACEHOLDER, HOMEBREW_CELLAR)
        .gsub(HOMEBREW_HOME_PLACEHOLDER, Dir.home)
end

Instance Method Details

#bin(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


10
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 10

def bin(*args, &block); end

#canonical_plist_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



96
97
98
# File 'service.rb', line 96

def canonical_plist_name
  "sh.brew.#{@formula.name}"
end

#canonical_service_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



118
119
120
# File 'service.rb', line 118

def canonical_service_name
  "sh.brew.#{@formula.name}"
end

#commandArray<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



568
569
570
# File 'service.rb', line 568

def command
  @run.map(&:to_s).map { |arg| arg.start_with?("~") ? File.expand_path(arg) : arg }
end

#command?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


573
574
575
# File 'service.rb', line 573

def command?
  !@run.empty?
end

#cron(value = nil) ⇒ Hash{Symbol => Integer, String}

Controls the trigger times, required for the :cron type.

Parameters:

  • value (String, nil) (defaults to: nil)

Returns:



420
421
422
423
424
425
426
# File 'service.rb', line 420

def cron(value = nil)
  if value
    @cron = parse_cron(value)
  else
    @cron
  end
end

#cron_weekday_to_systemd_weekday(cron_weekday) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



719
720
721
722
723
724
725
726
727
# File 'service.rb', line 719

def cron_weekday_to_systemd_weekday(cron_weekday)
  if cron_weekday != "*" &&
     (weekday_number = cron_weekday&.to_i) &&
     (0..7).cover?(weekday_number)
    "#{Date::ABBR_DAYNAMES[weekday_number % 7]} "
  else
    ""
  end
end

#default_cron_valuesHash{Symbol => Integer, String}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



429
430
431
432
433
434
435
436
437
# File 'service.rb', line 429

def default_cron_values
  {
    Month:   "*",
    Day:     "*",
    Weekday: "*",
    Hour:    "*",
    Minute:  "*",
  }
end

#default_plist_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



86
87
88
# File 'service.rb', line 86

def default_plist_name
  canonical_plist_name
end

#default_service_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



108
109
110
# File 'service.rb', line 108

def default_service_name
  canonical_service_name
end

#effective_environment_variablesHash{Symbol => String}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns the effective environment variables with user overrides merged in from $HOMEBREW_USER_CONFIG_HOME/services/<formula>.env. User overrides take precedence over formula-defined variables.

Returns:



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'service.rb', line 486

def effective_environment_variables
  env_vars = @environment_variables.dup

  env_file = Pathname.new("#{ENV.fetch("HOMEBREW_USER_CONFIG_HOME")}/services/#{@formula.name}.env")
  return env_vars unless env_file.file?

  # User env overrides are not supported for root services.
  # `sudo -E` can preserve a caller-controlled HOME, and TOCTOU
  # between symlink resolution, permission checks, and reading makes
  # it impossible to safely validate a user-owned override file when
  # generating a root service definition.
  if Process.euid.zero?
    opoo "Skipping #{env_file}: user env overrides are not supported for root services."
    return env_vars
  end

  if env_file.world_writable?
    opoo "Skipping #{env_file}: file is world-writable."
    return env_vars
  end

  if env_file.stat.mode.anybits?(020)
    opoo "Skipping #{env_file}: file is group-writable."
    return env_vars
  end

  # Read each line, strip whitespace, and remove blank lines and
  # comments (lines starting with `#`). Then parse remaining lines
  # as KEY=value pairs, warning on lines missing a `=` separator.
  overrides = env_file.each_line
                      .map(&:strip)
                      .reject { |line| line.empty? || line.start_with?("#") }
                      .each_with_object({}) do |line, hash|
                        key, value = line.split("=", 2)
                        if key.blank? || value.nil?
                          opoo "Skipping invalid line in #{env_file}: #{line}"
                          next
                        end

                        hash[key.strip] = value.strip
                      end

  overrides.each { |key, value| env_vars[key.to_sym] = value }

  env_vars
end

#environment_variables(variables = {}) ⇒ Hash{Symbol => String}?

Hash of variables to set.

Parameters:

Returns:



478
479
480
# File 'service.rb', line 478

def environment_variables(variables = {})
  @environment_variables = variables.transform_values(&:to_s)
end

#error_log_path(path = nil) ⇒ String?

Path to write stderr to.

Parameters:

Returns:



229
230
231
232
233
234
235
# File 'service.rb', line 229

def error_log_path(path = nil)
  if path
    @error_log_path = path.to_s
  else
    @error_log_path
  end
end

#etc(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


13
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 13

def etc(*args, &block); end

#fFormula

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



81
82
83
# File 'service.rb', line 81

def f
  @formula
end

#input_path(path = nil) ⇒ String?

Path to use as input for the process.

Parameters:

Returns:



205
206
207
208
209
210
211
# File 'service.rb', line 205

def input_path(path = nil)
  if path
    @input_path = path.to_s
  else
    @input_path
  end
end

#interval(value = nil) ⇒ Integer?

Controls the start interval, required for the :interval type.

Parameters:

  • value (Integer, nil) (defaults to: nil)

Returns:



408
409
410
411
412
413
414
# File 'service.rb', line 408

def interval(value = nil)
  if value
    @interval = value
  else
    @interval
  end
end

#keep_alive(value = nil) ⇒ Hash{Symbol => T.untyped}?

Sets contexts in which the service will keep the process running.

Parameters:

  • value (Boolean, Hash{Symbol => T.untyped}, nil) (defaults to: nil)

Returns:



244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'service.rb', line 244

def keep_alive(value = nil)
  case value
  when nil
    @keep_alive
  when true, false
    @keep_alive = { always: value }
  when Hash
    unless (value.keys - KEEP_ALIVE_KEYS).empty?
      raise TypeError, "Service#keep_alive only allows: #{KEEP_ALIVE_KEYS}"
    end

    @keep_alive = value
  end
end

#keep_alive?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a Boolean describing if a service is set to be kept alive.

Returns:

  • (Boolean)


321
322
323
# File 'service.rb', line 321

def keep_alive?
  !@keep_alive.empty? && @keep_alive[:always] != false
end

#launch_only_once(value = nil) ⇒ Boolean

Whether the command should only run once.

Parameters:

  • value (Boolean, nil) (defaults to: nil)

Returns:

  • (Boolean)


329
330
331
332
333
334
335
# File 'service.rb', line 329

def launch_only_once(value = nil)
  if value.nil?
    @launch_only_once
  else
    @launch_only_once = value
  end
end

#legacy_plist_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



91
92
93
# File 'service.rb', line 91

def legacy_plist_name
  "homebrew.mxcl.#{@formula.name}"
end

#legacy_service_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



113
114
115
# File 'service.rb', line 113

def legacy_service_name
  "homebrew.#{@formula.name}"
end

#libexec(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


16
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 16

def libexec(*args, &block); end

#log_path(path = nil) ⇒ String?

Path to write stdout to.

Parameters:

Returns:



217
218
219
220
221
222
223
# File 'service.rb', line 217

def log_path(path = nil)
  if path
    @log_path = path.to_s
  else
    @log_path
  end
end

#macos_legacy_timers(value = nil) ⇒ Boolean

Timers created by launchd jobs are coalesced unless this is set.

Parameters:

  • value (Boolean, nil) (defaults to: nil)

Returns:

  • (Boolean)


537
538
539
540
541
542
543
# File 'service.rb', line 537

def macos_legacy_timers(value = nil)
  if value.nil?
    @macos_legacy_timers
  else
    @macos_legacy_timers = value
  end
end

#manual_commandString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns the String command to run manually instead of the service.

Returns:



593
594
595
596
597
598
599
# File 'service.rb', line 593

def manual_command
  env_vars = effective_environment_variables.except(:PATH)
                                            .map { |k, v| "#{k}=\"#{v}\"" }

  env_vars.concat(command.map { |arg| Utils::Shell.sh_quote(arg) })
  env_vars.join(" ")
end

#name(macos: nil, linux: nil) ⇒ void

This method returns an undefined value.

A hash with the launchd service name on macOS and/or the systemd service name on Linux. Homebrew generates a default name for the service file if this is not present.

Parameters:

  • macos (String, nil) (defaults to: nil)
  • linux (String, nil) (defaults to: nil)

Raises:

  • (TypeError)


135
136
137
138
139
140
141
142
143
144
145
146
# File 'service.rb', line 135

def name(macos: nil, linux: nil)
  raise TypeError, "Service#name expects at least one String" if [macos, linux].none?(String)

  if macos
    @plist_name = macos
    @plist_name_explicitly_set = true
  end
  return unless linux

  @service_name = linux
  @service_name_explicitly_set = true
end

#nice(value = nil) ⇒ Integer?

Default scheduling priority (nice level), from -20 highest to 19 lowest. Note: Negative nice values (higher priority) require require_root: true to be set.

Parameters:

  • value (Integer, nil) (defaults to: nil)

Returns:

Raises:

  • (TypeError)


551
552
553
554
555
556
557
# File 'service.rb', line 551

def nice(value = nil)
  return @nice if value.nil?

  raise TypeError, "Service#nice value should be in #{NICE_RANGE}" unless NICE_RANGE.cover?(value)

  @nice = value
end

#nice_requires_root?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


76
77
78
# File 'service.rb', line 76

def nice_requires_root?
  @nice&.negative? == true && !requires_root?
end

#opt_bin(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


19
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 19

def opt_bin(*args, &block); end

#opt_libexec(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


22
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 22

def opt_libexec(*args, &block); end

#opt_pkgshare(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


25
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 25

def opt_pkgshare(*args, &block); end

#opt_prefix(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


28
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 28

def opt_prefix(*args, &block); end

#opt_sbin(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


31
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 31

def opt_sbin(*args, &block); end

#parse_cron(cron_statement) ⇒ Hash{Symbol => Integer, String}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'service.rb', line 440

def parse_cron(cron_statement)
  parsed = default_cron_values

  case cron_statement
  when "@hourly"
    parsed[:Minute] = 0
  when "@daily"
    parsed[:Minute] = 0
    parsed[:Hour] = 0
  when "@weekly"
    parsed[:Minute] = 0
    parsed[:Hour] = 0
    parsed[:Weekday] = 0
  when "@monthly"
    parsed[:Minute] = 0
    parsed[:Hour] = 0
    parsed[:Day] = 1
  when "@yearly", "@annually"
    parsed[:Minute] = 0
    parsed[:Hour] = 0
    parsed[:Day] = 1
    parsed[:Month] = 1
  else
    cron_parts = cron_statement.split
    raise TypeError, "Service#parse_cron expects a valid cron syntax" if cron_parts.length != 5

    [:Minute, :Hour, :Day, :Month, :Weekday].each_with_index do |selector, index|
      parsed[selector] = Integer(cron_parts.fetch(index)) if cron_parts.fetch(index) != "*"
    end
  end

  parsed
end

#path_dir(path) ⇒ Pathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



890
891
892
893
894
895
# File 'service.rb', line 890

def path_dir(path)
  return if path.blank?
  return unless path.start_with?("/", "~")

  Pathname.new(File.expand_path(path))
end

#path_dirsArray<Pathname>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



578
579
580
581
582
583
584
585
586
587
588
589
# File 'service.rb', line 578

def path_dirs
  [
    path_dir(@working_dir),
    path_dir(@root_dir),
  ].push(
    path_parent_dir(@input_path),
    path_parent_dir(@log_path),
    path_parent_dir(@error_log_path),
  )
   .compact
   .uniq
end

#path_parent_dir(path) ⇒ Pathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



898
899
900
# File 'service.rb', line 898

def path_parent_dir(path)
  path_dir(path)&.dirname
end

#plist_namesArray<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



101
102
103
104
105
# File 'service.rb', line 101

def plist_names
  return [plist_name] if @plist_name_explicitly_set

  [plist_name, canonical_plist_name, legacy_plist_name].uniq
end

#process_type(value = nil) ⇒ Symbol?

Type of process to manage: :background, :standard, :interactive or :adaptive.

Parameters:

  • value (Symbol, nil) (defaults to: nil)

Returns:



376
377
378
379
380
381
382
383
384
385
386
387
# File 'service.rb', line 376

def process_type(value = nil)
  case value
  when nil
    @process_type
  when :background, :standard, :interactive, :adaptive
    @process_type = value
  when Symbol
    raise TypeError, "Service#process_type allows: " \
                     "'#{PROCESS_TYPE_BACKGROUND}'/'#{PROCESS_TYPE_STANDARD}'/" \
                     "'#{PROCESS_TYPE_INTERACTIVE}'/'#{PROCESS_TYPE_ADAPTIVE}'"
  end
end

#require_root(value = nil) ⇒ Boolean

Whether the service requires root access. If true, Homebrew hints at using sudo on various occasions, but does not enforce it.

Parameters:

  • value (Boolean, nil) (defaults to: nil)

Returns:

  • (Boolean)


264
265
266
267
268
269
270
# File 'service.rb', line 264

def require_root(value = nil)
  if value.nil?
    @require_root
  else
    @require_root = value
  end
end

#requires_root?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a Boolean describing if a service requires root access.

Returns:

  • (Boolean)


274
275
276
# File 'service.rb', line 274

def requires_root?
  @require_root.present? && @require_root == true
end

#restart_delay(value = nil) ⇒ Integer?

Number of seconds to delay before restarting a process.

Parameters:

  • value (Integer, nil) (defaults to: nil)

Returns:



341
342
343
344
345
346
347
# File 'service.rb', line 341

def restart_delay(value = nil)
  if value
    @restart_delay = value
  else
    @restart_delay
  end
end

#root_dir(path = nil) ⇒ String?

Directory to use as a chroot for the process.

Parameters:

Returns:



193
194
195
196
197
198
199
# File 'service.rb', line 193

def root_dir(path = nil)
  if path
    @root_dir = path.to_s
  else
    @root_dir
  end
end

#run(command = nil, macos: nil, linux: nil) ⇒ Array<String>?

The command to execute: an array with arguments or a path.

Parameters:

  • command (RunParam, nil) (defaults to: nil)
  • macos (RunParam, nil) (defaults to: nil)
  • linux (RunParam, nil) (defaults to: nil)

Returns:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'service.rb', line 158

def run(command = nil, macos: nil, linux: nil)
  # Save parameters for serialization
  if command
    @run_params = command
  elsif macos || linux
    @run_params = { macos:, linux: }.compact
  end

  command ||= on_system_conditional(macos:, linux:)
  case command
  when nil
    @run
  when String, Pathname
    @run = [command.to_s]
  when Array
    @run = command.map(&:to_s)
  end
end

#run_at_load(value = nil) ⇒ Boolean?

Whether the command should run when the service is loaded.

Parameters:

  • value (Boolean, nil) (defaults to: nil)

Returns:

  • (Boolean, nil)


282
283
284
285
286
287
288
# File 'service.rb', line 282

def run_at_load(value = nil)
  if value.nil?
    @run_at_load
  else
    @run_at_load = value
  end
end

#run_type(value = nil) ⇒ Symbol?

The type of service: :immediate, :interval or :cron.

Parameters:

  • value (Symbol, nil) (defaults to: nil)

Returns:



393
394
395
396
397
398
399
400
401
402
# File 'service.rb', line 393

def run_type(value = nil)
  case value
  when nil
    @run_type
  when :immediate, :interval, :cron
    @run_type = value
  when Symbol
    raise TypeError, "Service#run_type allows: '#{RUN_TYPE_IMMEDIATE}'/'#{RUN_TYPE_INTERVAL}'/'#{RUN_TYPE_CRON}'"
  end
end

#service_namesArray<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



123
124
125
126
127
# File 'service.rb', line 123

def service_names
  return [service_name] if @service_name_explicitly_set

  [service_name, canonical_service_name, legacy_service_name].uniq
end

#sockets(value = nil) ⇒ Hash{Symbol => Hash{Symbol => String}}

Socket that is created as an access point to the service.

Parameters:

Returns:



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'service.rb', line 297

def sockets(value = nil)
  return @sockets if value.nil?

  value_hash = value.is_a?(String) ? { listeners: value } : value

  @sockets = value_hash.transform_values do |socket_string|
    match = socket_string.match(SOCKET_STRING_REGEX)
    host, port, type = match.values_at(:host, :port, :type) if match
    if host.nil? || port.nil? || type.nil?
      raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>"
    end

    begin
      IPAddr.new(host)
    rescue IPAddr::InvalidAddressError
      raise TypeError, "Service#sockets expects a valid ipv4 or ipv6 host address"
    end

    { host:, port:, type: }
  end
end

#std_service_path_envString

This method is part of an internal API. This method may only be used internally in repositories owned by Homebrew, except in casks or formulae. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



563
564
565
# File 'service.rb', line 563

def std_service_path_env
  "#{HOMEBREW_PREFIX}/bin:#{HOMEBREW_PREFIX}/sbin:/usr/bin:/bin:/usr/sbin:/sbin"
end

#stop_timeout(value = nil) ⇒ Integer?

Number of seconds to wait before forcibly stopping a process.

Parameters:

  • value (Integer, nil) (defaults to: nil)

Returns:

Raises:

  • (TypeError)


363
364
365
366
367
368
369
# File 'service.rb', line 363

def stop_timeout(value = nil)
  return @stop_timeout if value.nil?

  raise TypeError, "Service#stop_timeout must be a non-negative integer" if value.negative?

  @stop_timeout = value
end

#throttle_interval(value = nil) ⇒ Integer?

Minimum seconds to wait before invocations (macOS default is 10).

Parameters:

  • value (Integer, nil) (defaults to: nil)

Returns:



353
354
355
356
357
# File 'service.rb', line 353

def throttle_interval(value = nil)
  return @throttle_interval if value.nil?

  @throttle_interval = value
end

#timed?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a Boolean describing if a service is timed.

Returns:

  • (Boolean)


603
604
605
# File 'service.rb', line 603

def timed?
  @run_type == RUN_TYPE_CRON || @run_type == RUN_TYPE_INTERVAL
end

#to_hashHash{Symbol => T.untyped}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Prepare the service hash for inclusion in the formula API JSON.

Returns:



758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
# File 'service.rb', line 758

def to_hash
  name_params = {
    macos: (plist_name if @plist_name_explicitly_set),
    linux: (service_name if @service_name_explicitly_set),
  }.compact

  return { name: name_params }.compact_blank if @run_params.blank?

  cron_string = if @cron.present?
    [:Minute, :Hour, :Day, :Month, :Weekday]
      .filter_map { |key| @cron[key].to_s.presence }
      .join(" ")
  end

  sockets_var = unless @sockets.empty?
    @sockets.transform_values { |info| "#{info[:type]}://#{info[:host]}:#{info[:port]}" }
            .then do |sockets_hash|
              # TODO: Remove this code when all users are running on versions of Homebrew
              #       that can process sockets hashes (this commit or later).
              if sockets_hash.size == 1 && sockets_hash.key?(:listeners)
                # When original #sockets argument was a string: `sockets "tcp://127.0.0.1:80"`
                sockets_hash.fetch(:listeners)
              else
                # When original #sockets argument was a hash: `sockets http: "tcp://0.0.0.0:80"`
                sockets_hash
              end
            end
  end

  {
    name:                  name_params.presence,
    run:                   @run_params,
    run_type:              @run_type,
    interval:              @interval,
    cron:                  cron_string.presence,
    keep_alive:            @keep_alive,
    launch_only_once:      @launch_only_once,
    require_root:          @require_root,
    environment_variables: @environment_variables.presence,
    working_dir:           @working_dir,
    root_dir:              @root_dir,
    input_path:            @input_path,
    log_path:              @log_path,
    error_log_path:        @error_log_path,
    restart_delay:         @restart_delay,
    throttle_interval:     @throttle_interval,
    stop_timeout:          @stop_timeout,
    nice:                  @nice,
    process_type:          @process_type,
    macos_legacy_timers:   @macos_legacy_timers,
    sockets:               sockets_var,
  }.compact_blank
end

#to_plistString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a String plist.

Returns:



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
# File 'service.rb', line 609

def to_plist
  # command needs to be first because it initializes all other values
  base = {
    Label:            plist_name,
    ProgramArguments: command,
    RunAtLoad:        @run_at_load == true,
  }

  base[:LaunchOnlyOnce] = @launch_only_once if @launch_only_once == true
  base[:LegacyTimers] = @macos_legacy_timers if @macos_legacy_timers == true
  base[:ExitTimeOut] = @stop_timeout if @stop_timeout.present?
  base[:TimeOut] = @restart_delay if @restart_delay.present?
  base[:ThrottleInterval] = @throttle_interval if @throttle_interval.present?
  base[:ProcessType] = @process_type.to_s.capitalize if @process_type.present?
  base[:Nice] = @nice if @nice.present?
  base[:StartInterval] = @interval if @interval.present? && @run_type == RUN_TYPE_INTERVAL
  base[:WorkingDirectory] = File.expand_path(@working_dir) if @working_dir.present?
  base[:RootDirectory] = File.expand_path(@root_dir) if @root_dir.present?
  base[:StandardInPath] = File.expand_path(@input_path) if @input_path.present?
  base[:StandardOutPath] = File.expand_path(@log_path) if @log_path.present?
  base[:StandardErrorPath] = File.expand_path(@error_log_path) if @error_log_path.present?
  if (env_vars = effective_environment_variables).present?
    base[:EnvironmentVariables] = env_vars
  end

  if keep_alive?
    if (always = @keep_alive[:always].presence)
      base[:KeepAlive] = always
    elsif @keep_alive.key?(:successful_exit)
      base[:KeepAlive] = { SuccessfulExit: @keep_alive[:successful_exit] }
    elsif @keep_alive.key?(:crashed)
      base[:KeepAlive] = { Crashed: @keep_alive[:crashed] }
    elsif @keep_alive.key?(:path) && @keep_alive[:path].present?
      base[:KeepAlive] = { PathState: @keep_alive[:path].to_s }
    end
  end

  unless @sockets.empty?
    base[:Sockets] = {}
    @sockets.each do |name, info|
      base[:Sockets][name] = {
        SockNodeName:    info[:host],
        SockServiceName: info[:port],
        SockProtocol:    info[:type].upcase,
      }
    end
  end

  if !@cron.empty? && @run_type == RUN_TYPE_CRON
    base[:StartCalendarInterval] = @cron.reject { |_, value| value == "*" }
  end

  # Adding all session types has as the primary effect that if you initialise it through e.g. a Background session
  # and you later "physically" sign in to the owning account (Aqua session), things shouldn't flip out.
  # Also, we're not checking @process_type here because that is used to indicate process priority and not
  # necessarily if it should run in a specific session type. Like database services could run with ProcessType
  # Interactive so they have no resource limitations enforced upon them, but they aren't really interactive in the
  # general sense.
  base[:LimitLoadToSessionType] = %w[Aqua Background LoginWindow StandardIO System]

  require "plist"
  base.to_plist
end

#to_systemd_timerString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a String systemd unit timer.

Returns:



731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
# File 'service.rb', line 731

def to_systemd_timer
  options = []
  options << "Persistent=true" if @run_type == RUN_TYPE_CRON
  options << "OnUnitActiveSec=#{@interval}" if @run_type == RUN_TYPE_INTERVAL

  if @run_type == RUN_TYPE_CRON
    minutes = (@cron[:Minute] == "*") ? "*" : format("%02d", @cron[:Minute])
    hours   = (@cron[:Hour] == "*") ? "*" : format("%02d", @cron[:Hour])
    options << "OnCalendar=#{cron_weekday_to_systemd_weekday(@cron[:Weekday])}" \
               "*-#{@cron[:Month]}-#{@cron[:Day]} #{hours}:#{minutes}:00"
  end

  <<~SYSTEMD
    [Unit]
    Description=Homebrew generated timer for #{@formula.name}

    [Install]
    WantedBy=timers.target

    [Timer]
    Unit=#{service_name}.service
    #{options.join("\n")}
  SYSTEMD
end

#to_systemd_unitString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns a String systemd unit.

Returns:



675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
# File 'service.rb', line 675

def to_systemd_unit
  # command needs to be first because it initializes all other values
  cmd = command.map { |arg| Utils::Service.systemd_quote(arg) }
               .join(" ")

  options = []
  options << "Type=#{(@launch_only_once == true) ? "oneshot" : "simple"}"
  options << "ExecStart=#{cmd}"

  if @keep_alive.present?
    if @keep_alive[:always].present? || @keep_alive[:crashed].present?
      # Use on-failure instead of always to allow manual stops via systemctl
      options << "Restart=on-failure"
    elsif @keep_alive[:successful_exit].present?
      options << "Restart=on-success"
    end
  end
  options << "RestartSec=#{restart_delay}" if @restart_delay.present?
  options << "TimeoutStopSec=#{@stop_timeout}" if @stop_timeout.present?
  options << "Nice=#{@nice}" if @nice.present?
  options << "WorkingDirectory=#{File.expand_path(@working_dir)}" if @working_dir.present?
  options << "RootDirectory=#{File.expand_path(@root_dir)}" if @root_dir.present?
  options << "StandardInput=file:#{File.expand_path(@input_path)}" if @input_path.present?
  options << "StandardOutput=append:#{File.expand_path(@log_path)}" if @log_path.present?
  options << "StandardError=append:#{File.expand_path(@error_log_path)}" if @error_log_path.present?
  if (env_vars = effective_environment_variables).present?
    options += env_vars.map do |k, v|
      "Environment=\"#{k}=#{v}\""
    end
  end

  <<~SYSTEMD
    [Unit]
    Description=Homebrew generated unit for #{@formula.name}

    [Install]
    WantedBy=default.target

    [Service]
    #{options.join("\n")}
  SYSTEMD
end

#var(*args, &block) ⇒ T.untyped

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • args (T.untyped)
  • block (T.untyped)

Returns:

  • (T.untyped)


34
# File 'sorbet/rbi/dsl/homebrew/service.rbi', line 34

def var(*args, &block); end

#working_dir(path = nil) ⇒ String?

Directory to operate from.

Parameters:

Returns:



181
182
183
184
185
186
187
# File 'service.rb', line 181

def working_dir(path = nil)
  if path
    @working_dir = path.to_s
  else
    @working_dir
  end
end