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

child_of?, ensure_child_of!, formula_any_version_installed?, #formula_any_version_installed?, formula_installed_prefixes, formula_opt_bin, #formula_opt_bin, formula_opt_bin_env, formula_opt_bin_path, formula_opt_include, #formula_opt_include, formula_opt_lib, #formula_opt_lib, formula_opt_libexec, #formula_opt_libexec, formula_opt_prefix, #formula_opt_prefix, loadable_package_path?

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_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)


39
40
41
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
# File 'service.rb', line 39

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)
  @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)
  @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:



36
37
38
# File 'service.rb', line 36

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:



36
37
38
# File 'service.rb', line 36

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:



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
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
# File 'service.rb', line 771

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:



839
840
841
842
843
844
# File 'service.rb', line 839

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

#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:



525
526
527
# File 'service.rb', line 525

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)


530
531
532
# File 'service.rb', line 530

def command?
  !@run.empty?
end

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

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

Parameters:

  • value (String) (defaults to: T.unsafe(nil))

Returns:



377
378
379
380
381
382
383
# File 'service.rb', line 377

def cron(value = T.unsafe(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:



676
677
678
679
680
681
682
683
684
# File 'service.rb', line 676

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:



386
387
388
389
390
391
392
393
394
# File 'service.rb', line 386

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:



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

def default_plist_name
  "homebrew.mxcl.#{@formula.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:



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

def default_service_name
  "homebrew.#{@formula.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:



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
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'service.rb', line 443

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:



435
436
437
# File 'service.rb', line 435

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

#error_log_path(path = T.unsafe(nil)) ⇒ String?

Path to write stderr to.

Parameters:

Returns:



184
185
186
187
188
189
190
# File 'service.rb', line 184

def error_log_path(path = T.unsafe(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:



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

def f
  @formula
end

#input_path(path = T.unsafe(nil)) ⇒ String?

Path to use as input for the process.

Parameters:

Returns:



160
161
162
163
164
165
166
# File 'service.rb', line 160

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

#interval(value = T.unsafe(nil)) ⇒ Integer?

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

Parameters:

  • value (Integer) (defaults to: T.unsafe(nil))

Returns:



365
366
367
368
369
370
371
# File 'service.rb', line 365

def interval(value = T.unsafe(nil))
  if value
    @interval = value
  else
    @interval
  end
end

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

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

Parameters:

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

Returns:



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'service.rb', line 199

def keep_alive(value = T.unsafe(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)


278
279
280
# File 'service.rb', line 278

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

#launch_only_once(value = T.unsafe(nil)) ⇒ Boolean

Whether the command should only run once.

Parameters:

  • value (Boolean) (defaults to: T.unsafe(nil))

Returns:

  • (Boolean)


286
287
288
289
290
291
292
# File 'service.rb', line 286

def launch_only_once(value = T.unsafe(nil))
  if value.nil?
    @launch_only_once
  else
    @launch_only_once = value
  end
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 = T.unsafe(nil)) ⇒ String?

Path to write stdout to.

Parameters:

Returns:



172
173
174
175
176
177
178
# File 'service.rb', line 172

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

#macos_legacy_timers(value = T.unsafe(nil)) ⇒ Boolean

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

Parameters:

  • value (Boolean) (defaults to: T.unsafe(nil))

Returns:

  • (Boolean)


494
495
496
497
498
499
500
# File 'service.rb', line 494

def macos_legacy_timers(value = T.unsafe(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:



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

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)


96
97
98
99
100
101
# File 'service.rb', line 96

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

  @plist_name = macos if macos
  @service_name = linux if linux
end

#nice(value = T.unsafe(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) (defaults to: T.unsafe(nil))

Returns:

Raises:

  • (TypeError)


508
509
510
511
512
513
514
# File 'service.rb', line 508

def nice(value = T.unsafe(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)


71
72
73
# File 'service.rb', line 71

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:



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'service.rb', line 397

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:



847
848
849
850
851
852
# File 'service.rb', line 847

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:



535
536
537
538
539
540
541
542
543
544
545
546
# File 'service.rb', line 535

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:



855
856
857
# File 'service.rb', line 855

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

#process_type(value = T.unsafe(nil)) ⇒ Symbol?

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

Parameters:

  • value (Symbol) (defaults to: T.unsafe(nil))

Returns:



333
334
335
336
337
338
339
340
341
342
343
344
# File 'service.rb', line 333

def process_type(value = T.unsafe(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 = T.unsafe(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) (defaults to: T.unsafe(nil))

Returns:

  • (Boolean)


219
220
221
222
223
224
225
# File 'service.rb', line 219

def require_root(value = T.unsafe(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)


229
230
231
# File 'service.rb', line 229

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

#restart_delay(value = T.unsafe(nil)) ⇒ Integer?

Number of seconds to delay before restarting a process.

Parameters:

  • value (Integer) (defaults to: T.unsafe(nil))

Returns:



298
299
300
301
302
303
304
# File 'service.rb', line 298

def restart_delay(value = T.unsafe(nil))
  if value
    @restart_delay = value
  else
    @restart_delay
  end
end

#root_dir(path = T.unsafe(nil)) ⇒ String?

Directory to use as a chroot for the process.

Parameters:

Returns:



148
149
150
151
152
153
154
# File 'service.rb', line 148

def root_dir(path = T.unsafe(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:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'service.rb', line 113

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 = T.unsafe(nil)) ⇒ Boolean?

Whether the command should run when the service is loaded.

Parameters:

  • value (Boolean) (defaults to: T.unsafe(nil))

Returns:

  • (Boolean, nil)


237
238
239
240
241
242
243
# File 'service.rb', line 237

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

#run_type(value = T.unsafe(nil)) ⇒ Symbol?

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

Parameters:

  • value (Symbol) (defaults to: T.unsafe(nil))

Returns:



350
351
352
353
354
355
356
357
358
359
# File 'service.rb', line 350

def run_type(value = T.unsafe(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

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

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

Parameters:

Returns:



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'service.rb', line 252

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

  value_hash = case value
  when String
    { listeners: value }
  when Hash
    value
  end

  @sockets = T.must(value_hash).transform_values do |socket_string|
    match = socket_string.match(SOCKET_STRING_REGEX)
    raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>" unless match

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

    { host: match[:host], port: match[:port], type: match[: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:



520
521
522
# File 'service.rb', line 520

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

#stop_timeout(value = T.unsafe(nil)) ⇒ Integer?

Number of seconds to wait before forcibly stopping a process.

Parameters:

  • value (Integer) (defaults to: T.unsafe(nil))

Returns:

Raises:

  • (TypeError)


320
321
322
323
324
325
326
# File 'service.rb', line 320

def stop_timeout(value = T.unsafe(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 = T.unsafe(nil)) ⇒ Integer?

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

Parameters:

  • value (Integer) (defaults to: T.unsafe(nil))

Returns:



310
311
312
313
314
# File 'service.rb', line 310

def throttle_interval(value = T.unsafe(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)


560
561
562
# File 'service.rb', line 560

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:



715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'service.rb', line 715

def to_hash
  name_params = {
    macos: (plist_name if plist_name != default_plist_name),
    linux: (service_name if service_name != default_service_name),
  }.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:



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'service.rb', line 566

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:



688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# File 'service.rb', line 688

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:



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
672
673
# File 'service.rb', line 632

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 = T.unsafe(nil)) ⇒ String?

Directory to operate from.

Parameters:

Returns:



136
137
138
139
140
141
142
# File 'service.rb', line 136

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