# typed: strict # frozen_string_literal: true require "abstract_command" require "services/system" require "services/commands/list" require "services/commands/cleanup" require "services/commands/info" require "services/commands/restart" require "services/commands/run" require "services/commands/start" require "services/commands/stop" require "services/commands/kill" module Homebrew module Cmd class Services < AbstractCommand cmd_args do usage_banner <<~EOS `services` [] Manage background services with macOS' `launchctl`(1) daemon manager or Linux's `systemctl`(1) service manager. If `sudo` is passed, operate on `/Library/LaunchDaemons` or `/usr/lib/systemd/system` (started at boot). Otherwise, operate on `~/Library/LaunchAgents` or `~/.config/systemd/user` (started at login). [`sudo`] `brew services` [`list`] (`--json`) (`--debug`): List information about all managed services for the current user (or root). Provides more output from Homebrew and `launchctl`(1) or `systemctl`(1) if run with `--debug`. [`sudo`] `brew services info` (|`--all`|`--json`): List all managed services for the current user (or root). [`sudo`] `brew services run` (|`--all`|`--file=`): Run the service without registering to launch at login (or boot). [`sudo`] `brew services start` (|`--all`|`--file=`): Start the service immediately and register it to launch at login (or boot). [`sudo`] `brew services stop` (`--keep`) (`--no-wait`|`--max-wait=`) (|`--all`): Stop the service immediately and unregister it from launching at login (or boot), unless `--keep` is specified. [`sudo`] `brew services kill` (|`--all`): Stop the service immediately but keep it registered to launch at login (or boot). [`sudo`] `brew services restart` (|`--all`|`--file=`): Stop (if necessary) and start the service immediately and register it to launch at login (or boot). [`sudo`] `brew services cleanup`: Remove all unused services. EOS flag "--file=", description: "Use the service file from this location to `start` the service." flag "--sudo-service-user=", description: "When run as root on macOS, run the service(s) as this user." flag "--max-wait=", description: "Wait at most this many seconds for `stop` to finish stopping a service. " \ "Omit this flag or set this to zero (0) seconds to wait indefinitely." switch "--all", description: "Run on all services." switch "--json", description: "Output as JSON." switch "--no-wait", description: "Don't wait for `stop` to finish stopping the service." switch "--keep", description: "When stopped, don't unregister the service from launching at login (or boot)." conflicts "--max-wait=", "--no-wait" named_args %w[list info run start stop kill restart cleanup] end sig { override.void } def run # pbpaste's exit status is a proxy for detecting the use of reattach-to-user-namespace if ENV.fetch("HOMEBREW_TMUX", nil) && File.exist?("/usr/bin/pbpaste") && !quiet_system("/usr/bin/pbpaste") raise UsageError, "`brew services` cannot run under tmux!" end # Keep this after the .parse to keep --help fast. require "utils" if !Homebrew::Services::System.launchctl? && !Homebrew::Services::System.systemctl? raise UsageError, "`brew services` is supported only on macOS or Linux (with systemd)!" end if (sudo_service_user = args.sudo_service_user) unless Homebrew::Services::System.root? raise UsageError, "`brew services` is supported only when running as root!" end unless Homebrew::Services::System.launchctl? raise UsageError, "`brew services --sudo-service-user` is currently supported only on macOS " \ "(but we'd love a PR to add Linux support)!" end Homebrew::Services::Cli.sudo_service_user = sudo_service_user end # Parse arguments. subcommand, *formulae = args.named no_named_formula_commands = [ *Homebrew::Services::Commands::List::TRIGGERS, *Homebrew::Services::Commands::Cleanup::TRIGGERS, ] if no_named_formula_commands.include?(subcommand) raise UsageError, "The `#{subcommand}` subcommand does not accept a formula argument!" if formulae.present? raise UsageError, "The `#{subcommand}` subcommand does not accept the --all argument!" if args.all? end if args.file file_commands = [ *Homebrew::Services::Commands::Start::TRIGGERS, *Homebrew::Services::Commands::Run::TRIGGERS, *Homebrew::Services::Commands::Restart::TRIGGERS, ] if file_commands.exclude?(subcommand) raise UsageError, "The `#{subcommand}` subcommand does not accept the --file= argument!" elsif args.all? raise UsageError, "The `#{subcommand}` subcommand does not accept the --all and --file= arguments at the same time!" end end unless Homebrew::Services::Commands::Stop::TRIGGERS.include?(subcommand) raise UsageError, "The `#{subcommand}` subcommand does not accept the --keep argument!" if args.keep? raise UsageError, "The `#{subcommand}` subcommand does not accept the --no-wait argument!" if args.no_wait? if args.max_wait raise UsageError, "The `#{subcommand}` subcommand does not accept the --max-wait= argument!" end end opoo "The --all argument overrides provided formula argument!" if formulae.present? && args.all? targets = if args.all? if subcommand == "start" Homebrew::Services::Formulae.available_services( loaded: false, skip_root: !Homebrew::Services::System.root?, ) elsif subcommand == "stop" Homebrew::Services::Formulae.available_services( loaded: true, skip_root: !Homebrew::Services::System.root?, ) else Homebrew::Services::Formulae.available_services end elsif formulae.present? formulae.map { |formula| Homebrew::Services::FormulaWrapper.new(Formulary.factory(formula)) } else [] end # Exit successfully if --all was used but there is nothing to do return if args.all? && targets.empty? if Homebrew::Services::System.systemctl? ENV["DBUS_SESSION_BUS_ADDRESS"] = ENV.fetch("HOMEBREW_DBUS_SESSION_BUS_ADDRESS", nil) ENV["XDG_RUNTIME_DIR"] = ENV.fetch("HOMEBREW_XDG_RUNTIME_DIR", nil) end # Dispatch commands and aliases. case subcommand.presence when *Homebrew::Services::Commands::List::TRIGGERS Homebrew::Services::Commands::List.run(json: args.json?) when *Homebrew::Services::Commands::Cleanup::TRIGGERS Homebrew::Services::Commands::Cleanup.run when *Homebrew::Services::Commands::Info::TRIGGERS Homebrew::Services::Commands::Info.run(targets, verbose: args.verbose?, json: args.json?) when *Homebrew::Services::Commands::Restart::TRIGGERS Homebrew::Services::Commands::Restart.run(targets, args.file, verbose: args.verbose?) when *Homebrew::Services::Commands::Run::TRIGGERS Homebrew::Services::Commands::Run.run(targets, args.file, verbose: args.verbose?) when *Homebrew::Services::Commands::Start::TRIGGERS Homebrew::Services::Commands::Start.run(targets, args.file, verbose: args.verbose?) when *Homebrew::Services::Commands::Stop::TRIGGERS Homebrew::Services::Commands::Stop.run( targets, verbose: args.verbose?, no_wait: args.no_wait?, max_wait: args.max_wait.to_f, keep: args.keep?, ) when *Homebrew::Services::Commands::Kill::TRIGGERS Homebrew::Services::Commands::Kill.run(targets, verbose: args.verbose?) else raise UsageError, "unknown subcommand: `#{subcommand}`" end end end end end