2020-12-11 23:14:50 +01:00
|
|
|
# typed: true
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-01-13 14:40:03 +01:00
|
|
|
require "extend/on_system"
|
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
module Homebrew
|
|
|
|
# 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.
|
|
|
|
class Service
|
|
|
|
extend T::Sig
|
2021-04-08 10:06:45 +02:00
|
|
|
extend Forwardable
|
2023-01-13 14:40:03 +01:00
|
|
|
include OnSystem::MacOSAndLinux
|
2020-12-11 23:14:50 +01:00
|
|
|
|
2021-11-02 17:17:45 +01:00
|
|
|
RUN_TYPE_IMMEDIATE = :immediate
|
|
|
|
RUN_TYPE_INTERVAL = :interval
|
|
|
|
RUN_TYPE_CRON = :cron
|
2020-12-11 23:14:50 +01:00
|
|
|
|
2021-11-02 17:17:45 +01:00
|
|
|
PROCESS_TYPE_BACKGROUND = :background
|
|
|
|
PROCESS_TYPE_STANDARD = :standard
|
|
|
|
PROCESS_TYPE_INTERACTIVE = :interactive
|
|
|
|
PROCESS_TYPE_ADAPTIVE = :adaptive
|
2021-09-08 21:54:13 -07:00
|
|
|
|
2022-01-25 19:29:16 +01:00
|
|
|
KEEP_ALIVE_KEYS = [:always, :successful_exit, :crashed, :path].freeze
|
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
# sig { params(formula: Formula).void }
|
2021-04-08 10:06:45 +02:00
|
|
|
def initialize(formula, &block)
|
2020-12-11 23:14:50 +01:00
|
|
|
@formula = formula
|
|
|
|
@run_type = RUN_TYPE_IMMEDIATE
|
2023-02-23 10:15:06 -08:00
|
|
|
@run_at_load = true
|
2020-12-11 23:14:50 +01:00
|
|
|
@environment_variables = {}
|
2021-04-08 10:06:45 +02:00
|
|
|
@service_block = block
|
2020-12-11 23:14:50 +01:00
|
|
|
end
|
|
|
|
|
2022-08-16 08:35:33 +01:00
|
|
|
sig { returns(Formula) }
|
|
|
|
def f
|
|
|
|
@formula
|
|
|
|
end
|
|
|
|
|
2023-01-13 14:40:03 +01:00
|
|
|
sig {
|
|
|
|
params(
|
|
|
|
command: T.nilable(T.any(T::Array[String], String, Pathname)),
|
|
|
|
macos: T.nilable(T.any(T::Array[String], String, Pathname)),
|
|
|
|
linux: T.nilable(T.any(T::Array[String], String, Pathname)),
|
|
|
|
).returns(T.nilable(Array))
|
|
|
|
}
|
|
|
|
def run(command = nil, macos: nil, linux: nil)
|
2023-03-17 23:24:37 -07:00
|
|
|
# Save parameters for serialization
|
|
|
|
@run_params ||= command || { macos: macos, linux: linux }.compact
|
|
|
|
|
2023-01-13 14:40:03 +01:00
|
|
|
command ||= on_system_conditional(macos: macos, linux: linux)
|
2020-12-11 23:14:50 +01:00
|
|
|
case T.unsafe(command)
|
|
|
|
when nil
|
|
|
|
@run
|
|
|
|
when String, Pathname
|
|
|
|
@run = [command]
|
|
|
|
when Array
|
|
|
|
@run = command
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#run expects an Array"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T.nilable(String)) }
|
|
|
|
def working_dir(path = nil)
|
|
|
|
case T.unsafe(path)
|
|
|
|
when nil
|
|
|
|
@working_dir
|
|
|
|
when String, Pathname
|
|
|
|
@working_dir = path.to_s
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#working_dir expects a String"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-22 18:17:36 +02:00
|
|
|
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T.nilable(String)) }
|
|
|
|
def root_dir(path = nil)
|
|
|
|
case T.unsafe(path)
|
|
|
|
when nil
|
|
|
|
@root_dir
|
|
|
|
when String, Pathname
|
|
|
|
@root_dir = path.to_s
|
|
|
|
else
|
2021-05-24 14:20:46 +02:00
|
|
|
raise TypeError, "Service#root_dir expects a String or Pathname"
|
2021-05-22 18:17:36 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T.nilable(String)) }
|
|
|
|
def input_path(path = nil)
|
|
|
|
case T.unsafe(path)
|
|
|
|
when nil
|
|
|
|
@input_path
|
|
|
|
when String, Pathname
|
|
|
|
@input_path = path.to_s
|
|
|
|
else
|
2021-05-24 14:20:46 +02:00
|
|
|
raise TypeError, "Service#input_path expects a String or Pathname"
|
2021-05-22 18:17:36 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T.nilable(String)) }
|
|
|
|
def log_path(path = nil)
|
|
|
|
case T.unsafe(path)
|
|
|
|
when nil
|
|
|
|
@log_path
|
|
|
|
when String, Pathname
|
|
|
|
@log_path = path.to_s
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#log_path expects a String"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { params(path: T.nilable(T.any(String, Pathname))).returns(T.nilable(String)) }
|
|
|
|
def error_log_path(path = nil)
|
|
|
|
case T.unsafe(path)
|
|
|
|
when nil
|
|
|
|
@error_log_path
|
|
|
|
when String, Pathname
|
|
|
|
@error_log_path = path.to_s
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#error_log_path expects a String"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-01-25 19:29:16 +01:00
|
|
|
sig {
|
|
|
|
params(value: T.nilable(T.any(T::Boolean, T::Hash[Symbol, T.untyped])))
|
|
|
|
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
|
|
|
}
|
2020-12-11 23:14:50 +01:00
|
|
|
def keep_alive(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@keep_alive
|
|
|
|
when true, false
|
2022-01-25 19:29:16 +01:00
|
|
|
@keep_alive = { always: value }
|
|
|
|
when Hash
|
|
|
|
hash = T.cast(value, Hash)
|
|
|
|
unless (hash.keys - KEEP_ALIVE_KEYS).empty?
|
|
|
|
raise TypeError, "Service#keep_alive allows only #{KEEP_ALIVE_KEYS}"
|
|
|
|
end
|
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
@keep_alive = value
|
|
|
|
else
|
2022-01-25 19:29:16 +01:00
|
|
|
raise TypeError, "Service#keep_alive expects a Boolean or Hash"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-06 13:52:53 +01:00
|
|
|
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
|
|
|
|
def require_root(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@require_root
|
|
|
|
when true, false
|
|
|
|
@require_root = value
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#require_root expects a Boolean"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns a `Boolean` describing if a service requires root access.
|
|
|
|
# @return [Boolean]
|
|
|
|
sig { returns(T::Boolean) }
|
|
|
|
def requires_root?
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2022-11-06 13:52:53 +01:00
|
|
|
@require_root.present? && @require_root == true
|
|
|
|
end
|
|
|
|
|
2023-02-13 23:19:34 -08:00
|
|
|
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
|
|
|
|
def run_at_load(value = nil)
|
|
|
|
case T.unsafe(value)
|
2023-02-14 04:24:26 -08:00
|
|
|
when nil
|
2023-02-13 23:19:34 -08:00
|
|
|
@run_at_load
|
2023-02-14 04:24:26 -08:00
|
|
|
when true, false
|
|
|
|
@run_at_load = value
|
2023-02-13 23:19:34 -08:00
|
|
|
else
|
|
|
|
raise TypeError, "Service#run_at_load expects a Boolean"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-01-25 19:29:16 +01:00
|
|
|
sig { params(value: T.nilable(String)).returns(T.nilable(T::Hash[Symbol, String])) }
|
|
|
|
def sockets(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@sockets
|
|
|
|
when String
|
|
|
|
match = T.must(value).match(%r{([a-z]+)://([a-z0-9.]+):([0-9]+)}i)
|
|
|
|
raise TypeError, "Service#sockets a formatted socket definition as <type>://<host>:<port>" if match.blank?
|
|
|
|
|
|
|
|
type, host, port = match.captures
|
|
|
|
@sockets = { host: host, port: port, type: type }
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#sockets expects a String"
|
2020-12-11 23:14:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-11 12:42:41 -08:00
|
|
|
# Returns a `Boolean` describing if a service is set to be kept alive.
|
|
|
|
# @return [Boolean]
|
|
|
|
sig { returns(T::Boolean) }
|
|
|
|
def keep_alive?
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2022-01-25 19:29:16 +01:00
|
|
|
@keep_alive.present? && @keep_alive[:always] != false
|
2022-03-11 12:42:41 -08:00
|
|
|
end
|
|
|
|
|
2022-02-04 11:37:29 -06:00
|
|
|
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
|
|
|
|
def launch_only_once(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@launch_only_once
|
|
|
|
when true, false
|
|
|
|
@launch_only_once = value
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#launch_only_once expects a Boolean"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-22 18:17:36 +02:00
|
|
|
sig { params(value: T.nilable(Integer)).returns(T.nilable(Integer)) }
|
|
|
|
def restart_delay(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@restart_delay
|
|
|
|
when Integer
|
|
|
|
@restart_delay = value
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#restart_delay expects an Integer"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-02 17:17:45 +01:00
|
|
|
sig { params(value: T.nilable(Symbol)).returns(T.nilable(Symbol)) }
|
|
|
|
def process_type(value = nil)
|
|
|
|
case T.unsafe(value)
|
2021-09-08 21:54:13 -07:00
|
|
|
when nil
|
|
|
|
@process_type
|
2021-11-02 17:17:45 +01:00
|
|
|
when :background, :standard, :interactive, :adaptive
|
|
|
|
@process_type = value
|
|
|
|
when Symbol
|
2022-06-28 10:09:59 +01:00
|
|
|
raise TypeError, "Service#process_type allows: " \
|
|
|
|
"'#{PROCESS_TYPE_BACKGROUND}'/'#{PROCESS_TYPE_STANDARD}'/" \
|
2021-09-08 21:54:13 -07:00
|
|
|
"'#{PROCESS_TYPE_INTERACTIVE}'/'#{PROCESS_TYPE_ADAPTIVE}'"
|
|
|
|
else
|
2021-11-02 17:17:45 +01:00
|
|
|
raise TypeError, "Service#process_type expects a Symbol"
|
2021-09-08 21:54:13 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-02 17:17:45 +01:00
|
|
|
sig { params(value: T.nilable(Symbol)).returns(T.nilable(Symbol)) }
|
|
|
|
def run_type(value = nil)
|
|
|
|
case T.unsafe(value)
|
2020-12-11 23:14:50 +01:00
|
|
|
when nil
|
|
|
|
@run_type
|
2021-11-21 14:25:40 +01:00
|
|
|
when :immediate, :interval, :cron
|
2021-11-02 17:17:45 +01:00
|
|
|
@run_type = value
|
|
|
|
when Symbol
|
2020-12-11 23:14:50 +01:00
|
|
|
raise TypeError, "Service#run_type allows: '#{RUN_TYPE_IMMEDIATE}'/'#{RUN_TYPE_INTERVAL}'/'#{RUN_TYPE_CRON}'"
|
|
|
|
else
|
2021-11-02 17:17:45 +01:00
|
|
|
raise TypeError, "Service#run_type expects a Symbol"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { params(value: T.nilable(Integer)).returns(T.nilable(Integer)) }
|
|
|
|
def interval(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@interval
|
|
|
|
when Integer
|
|
|
|
@interval = value
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#interval expects an Integer"
|
2020-12-11 23:14:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-21 14:25:40 +01:00
|
|
|
sig { params(value: T.nilable(String)).returns(T.nilable(Hash)) }
|
|
|
|
def cron(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@cron
|
|
|
|
when String
|
|
|
|
@cron = parse_cron(T.must(value))
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#cron expects a String"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { returns(T::Hash[Symbol, T.any(Integer, String)]) }
|
|
|
|
def default_cron_values
|
|
|
|
{
|
|
|
|
Month: "*",
|
|
|
|
Day: "*",
|
|
|
|
Weekday: "*",
|
|
|
|
Hour: "*",
|
|
|
|
Minute: "*",
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
sig { params(cron_statement: String).returns(T::Hash[Symbol, T.any(Integer, String)]) }
|
|
|
|
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
|
|
|
|
|
2023-03-17 23:24:37 -07:00
|
|
|
sig { params(variables: T::Hash[Symbol, String]).returns(T.nilable(T::Hash[Symbol, String])) }
|
2020-12-11 23:14:50 +01:00
|
|
|
def environment_variables(variables = {})
|
|
|
|
case T.unsafe(variables)
|
|
|
|
when Hash
|
2021-05-22 10:43:39 +02:00
|
|
|
@environment_variables = variables.transform_values(&:to_s)
|
2020-12-11 23:14:50 +01:00
|
|
|
else
|
|
|
|
raise TypeError, "Service#environment_variables expects a hash"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-22 18:17:36 +02:00
|
|
|
sig { params(value: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) }
|
|
|
|
def macos_legacy_timers(value = nil)
|
|
|
|
case T.unsafe(value)
|
|
|
|
when nil
|
|
|
|
@macos_legacy_timers
|
|
|
|
when true, false
|
|
|
|
@macos_legacy_timers = value
|
|
|
|
else
|
|
|
|
raise TypeError, "Service#macos_legacy_timers expects a Boolean"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-10 00:00:24 +05:30
|
|
|
delegate [:bin, :etc, :libexec, :opt_bin, :opt_libexec, :opt_pkgshare, :opt_prefix, :opt_sbin, :var] => :@formula
|
2020-12-11 23:14:50 +01:00
|
|
|
|
|
|
|
sig { returns(String) }
|
|
|
|
def std_service_path_env
|
|
|
|
"#{HOMEBREW_PREFIX}/bin:#{HOMEBREW_PREFIX}/sbin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
|
|
end
|
|
|
|
|
2023-01-13 14:40:03 +01:00
|
|
|
sig { returns(T.nilable(T::Array[String])) }
|
2021-04-13 11:25:56 +02:00
|
|
|
def command
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2023-01-13 14:40:03 +01:00
|
|
|
@run&.map(&:to_s)
|
2021-04-13 11:25:56 +02:00
|
|
|
end
|
|
|
|
|
2021-11-20 15:14:50 +01:00
|
|
|
# Returns the `String` command to run manually instead of the service.
|
|
|
|
# @return [String]
|
2021-05-22 10:59:28 +02:00
|
|
|
sig { returns(String) }
|
|
|
|
def manual_command
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2021-05-22 10:59:28 +02:00
|
|
|
vars = @environment_variables.except(:PATH)
|
|
|
|
.map { |k, v| "#{k}=\"#{v}\"" }
|
|
|
|
|
2023-01-13 14:40:03 +01:00
|
|
|
cmd = command
|
|
|
|
out = vars + cmd if cmd.present?
|
2021-05-22 10:59:28 +02:00
|
|
|
out.join(" ")
|
|
|
|
end
|
|
|
|
|
2021-11-20 15:14:50 +01:00
|
|
|
# Returns a `Boolean` describing if a service is timed.
|
|
|
|
# @return [Boolean]
|
|
|
|
sig { returns(T::Boolean) }
|
|
|
|
def timed?
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2021-11-20 15:14:50 +01:00
|
|
|
@run_type == RUN_TYPE_CRON || @run_type == RUN_TYPE_INTERVAL
|
|
|
|
end
|
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
# Returns a `String` plist.
|
|
|
|
# @return [String]
|
|
|
|
sig { returns(String) }
|
|
|
|
def to_plist
|
2021-11-02 17:17:45 +01:00
|
|
|
# command needs to be first because it initializes all other values
|
2020-12-11 23:14:50 +01:00
|
|
|
base = {
|
|
|
|
Label: @formula.plist_name,
|
2021-04-29 09:43:39 +02:00
|
|
|
ProgramArguments: command,
|
2023-02-23 10:15:06 -08:00
|
|
|
RunAtLoad: @run_at_load == true,
|
2020-12-11 23:14:50 +01:00
|
|
|
}
|
|
|
|
|
2022-02-04 11:42:25 -06:00
|
|
|
base[:LaunchOnlyOnce] = @launch_only_once if @launch_only_once == true
|
2021-05-22 18:17:36 +02:00
|
|
|
base[:LegacyTimers] = @macos_legacy_timers if @macos_legacy_timers == true
|
|
|
|
base[:TimeOut] = @restart_delay if @restart_delay.present?
|
2021-11-02 17:17:45 +01:00
|
|
|
base[:ProcessType] = @process_type.to_s.capitalize if @process_type.present?
|
|
|
|
base[:StartInterval] = @interval if @interval.present? && @run_type == RUN_TYPE_INTERVAL
|
2020-12-11 23:14:50 +01:00
|
|
|
base[:WorkingDirectory] = @working_dir if @working_dir.present?
|
2021-05-22 18:17:36 +02:00
|
|
|
base[:RootDirectory] = @root_dir if @root_dir.present?
|
|
|
|
base[:StandardInPath] = @input_path if @input_path.present?
|
2020-12-11 23:14:50 +01:00
|
|
|
base[:StandardOutPath] = @log_path if @log_path.present?
|
|
|
|
base[:StandardErrorPath] = @error_log_path if @error_log_path.present?
|
|
|
|
base[:EnvironmentVariables] = @environment_variables unless @environment_variables.empty?
|
|
|
|
|
2022-01-25 19:29:16 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
if @sockets.present?
|
|
|
|
base[:Sockets] = {}
|
|
|
|
base[:Sockets][:Listeners] = {
|
|
|
|
SockNodeName: @sockets[:host],
|
|
|
|
SockServiceName: @sockets[:port],
|
|
|
|
SockProtocol: @sockets[:type].upcase,
|
|
|
|
SockFamily: "IPv4v6",
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-11-21 14:25:40 +01:00
|
|
|
if @cron.present? && @run_type == RUN_TYPE_CRON
|
|
|
|
base[:StartCalendarInterval] = @cron.reject { |_, value| value == "*" }
|
|
|
|
end
|
|
|
|
|
2022-07-03 16:24:05 +02:00
|
|
|
# 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]
|
2022-07-03 15:56:49 +02:00
|
|
|
|
2020-12-11 23:14:50 +01:00
|
|
|
base.to_plist
|
|
|
|
end
|
2021-04-13 16:59:59 +02:00
|
|
|
|
|
|
|
# Returns a `String` systemd unit.
|
|
|
|
# @return [String]
|
|
|
|
sig { returns(String) }
|
|
|
|
def to_systemd_unit
|
|
|
|
unit = <<~EOS
|
|
|
|
[Unit]
|
|
|
|
Description=Homebrew generated unit for #{@formula.name}
|
|
|
|
|
2021-06-05 17:59:17 +02:00
|
|
|
[Install]
|
2022-09-21 15:13:28 +02:00
|
|
|
WantedBy=default.target
|
2021-06-05 17:59:17 +02:00
|
|
|
|
2021-04-13 16:59:59 +02:00
|
|
|
[Service]
|
|
|
|
EOS
|
|
|
|
|
2022-02-11 13:59:24 -06:00
|
|
|
# command needs to be first because it initializes all other values
|
2023-01-13 14:40:03 +01:00
|
|
|
cmd = command&.join(" ")
|
2022-02-11 13:59:24 -06:00
|
|
|
|
2021-04-13 16:59:59 +02:00
|
|
|
options = []
|
2022-08-10 14:19:33 +01:00
|
|
|
options << "Type=#{(@launch_only_once == true) ? "oneshot" : "simple"}"
|
2022-02-11 13:59:24 -06:00
|
|
|
options << "ExecStart=#{cmd}"
|
2022-01-25 19:29:16 +01:00
|
|
|
|
|
|
|
options << "Restart=always" if @keep_alive.present? && @keep_alive[:always].present?
|
2021-05-22 18:17:36 +02:00
|
|
|
options << "RestartSec=#{restart_delay}" if @restart_delay.present?
|
2021-04-13 16:59:59 +02:00
|
|
|
options << "WorkingDirectory=#{@working_dir}" if @working_dir.present?
|
2021-05-22 18:17:36 +02:00
|
|
|
options << "RootDirectory=#{@root_dir}" if @root_dir.present?
|
|
|
|
options << "StandardInput=file:#{@input_path}" if @input_path.present?
|
2021-04-13 16:59:59 +02:00
|
|
|
options << "StandardOutput=append:#{@log_path}" if @log_path.present?
|
|
|
|
options << "StandardError=append:#{@error_log_path}" if @error_log_path.present?
|
2021-05-25 11:46:18 +02:00
|
|
|
options += @environment_variables.map { |k, v| "Environment=\"#{k}=#{v}\"" } if @environment_variables.present?
|
2021-04-13 16:59:59 +02:00
|
|
|
|
|
|
|
unit + options.join("\n")
|
|
|
|
end
|
2021-11-20 15:14:50 +01:00
|
|
|
|
|
|
|
# Returns a `String` systemd unit timer.
|
|
|
|
# @return [String]
|
|
|
|
sig { returns(String) }
|
|
|
|
def to_systemd_timer
|
|
|
|
timer = <<~EOS
|
|
|
|
[Unit]
|
|
|
|
Description=Homebrew generated timer for #{@formula.name}
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
WantedBy=timers.target
|
|
|
|
|
|
|
|
[Timer]
|
|
|
|
Unit=#{@formula.service_name}
|
|
|
|
EOS
|
|
|
|
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
2021-11-20 15:14:50 +01:00
|
|
|
options = []
|
2021-11-21 14:25:40 +01:00
|
|
|
options << "Persistent=true" if @run_type == RUN_TYPE_CRON
|
2021-11-20 15:14:50 +01:00
|
|
|
options << "OnUnitActiveSec=#{@interval}" if @run_type == RUN_TYPE_INTERVAL
|
|
|
|
|
2021-11-21 14:25:40 +01:00
|
|
|
if @run_type == RUN_TYPE_CRON
|
2022-08-10 14:19:33 +01:00
|
|
|
minutes = (@cron[:Minute] == "*") ? "*" : format("%02d", @cron[:Minute])
|
|
|
|
hours = (@cron[:Hour] == "*") ? "*" : format("%02d", @cron[:Hour])
|
2021-11-21 14:25:40 +01:00
|
|
|
options << "OnCalendar=#{@cron[:Weekday]}-*-#{@cron[:Month]}-#{@cron[:Day]} #{hours}:#{minutes}:00"
|
|
|
|
end
|
|
|
|
|
2021-11-20 15:14:50 +01:00
|
|
|
timer + options.join("\n")
|
|
|
|
end
|
2023-03-17 23:24:37 -07:00
|
|
|
|
|
|
|
# Only evaluate the service block once.
|
|
|
|
sig { void }
|
|
|
|
def eval_service_block
|
|
|
|
return if @eval_service_block
|
|
|
|
|
|
|
|
instance_eval(&@service_block)
|
|
|
|
@eval_service_block = true
|
|
|
|
end
|
|
|
|
|
2023-03-21 21:53:52 -07:00
|
|
|
# Prepare the service hash for inclusion in the formula API JSON.
|
2023-03-17 23:24:37 -07:00
|
|
|
sig { returns(Hash) }
|
2023-03-21 21:53:52 -07:00
|
|
|
def serialize
|
2023-03-17 23:24:37 -07:00
|
|
|
eval_service_block
|
|
|
|
|
|
|
|
cron_string = if @cron.present?
|
|
|
|
[:Minute, :Hour, :Day, :Month, :Weekday]
|
|
|
|
.map { |key| @cron[key].to_s }
|
|
|
|
.join(" ")
|
|
|
|
end
|
|
|
|
|
|
|
|
sockets_string = "#{@sockets[:type]}://#{@sockets[:host]}:#{@sockets[:port]}" if @sockets.present?
|
|
|
|
|
|
|
|
{
|
2023-03-21 21:53:52 -07:00
|
|
|
run: @run_params,
|
|
|
|
run_type: @run_type,
|
|
|
|
interval: @interval,
|
|
|
|
cron: cron_string,
|
|
|
|
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,
|
|
|
|
process_type: @process_type,
|
|
|
|
macos_legacy_timers: @macos_legacy_timers,
|
|
|
|
sockets: sockets_string,
|
2023-03-17 23:24:37 -07:00
|
|
|
}.compact
|
|
|
|
end
|
2023-03-21 21:53:52 -07:00
|
|
|
|
|
|
|
# Turn the service API hash values back into what is expected by the formula DSL.
|
|
|
|
sig { params(api_hash: Hash).returns(Hash) }
|
|
|
|
def self.deserialize(api_hash)
|
|
|
|
hash = {}
|
|
|
|
hash[:run] =
|
|
|
|
case api_hash["run"]
|
|
|
|
when Hash
|
|
|
|
api_hash["run"].to_h do |key, array|
|
|
|
|
[
|
|
|
|
key.to_sym,
|
|
|
|
array.map { |value| replace_placeholders(value) },
|
|
|
|
]
|
|
|
|
end
|
|
|
|
when Array
|
|
|
|
api_hash["run"].map { |value| replace_placeholders(value) }
|
|
|
|
end
|
|
|
|
|
|
|
|
hash[:keep_alive] = api_hash["keep_alive"].transform_keys(&:to_sym) if api_hash.key?("keep_alive")
|
|
|
|
|
|
|
|
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 macos_legacy_timers sockets].each do |key|
|
|
|
|
next if (value = api_hash[key]).nil?
|
|
|
|
|
|
|
|
hash[key.to_sym] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
|
|
|
|
# Replace API path placeholders with local paths.
|
|
|
|
sig { params(string: String).returns(String) }
|
|
|
|
def self.replace_placeholders(string)
|
|
|
|
string.gsub(HOMEBREW_PREFIX_PLACEHOLDER, HOMEBREW_PREFIX)
|
|
|
|
.gsub(HOMEBREW_HOME_PLACEHOLDER, Dir.home)
|
|
|
|
end
|
2020-12-11 23:14:50 +01:00
|
|
|
end
|
|
|
|
end
|