diff --git a/cmd/services.rb b/cmd/services.rb index 856bd7bb0..e22629cda 100755 --- a/cmd/services.rb +++ b/cmd/services.rb @@ -40,6 +40,7 @@ def services_args 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." switch "--all", description: "Run on all services." switch "--json", description: "Output as JSON." end @@ -63,6 +64,21 @@ def services "`brew services` is supported only on macOS or Linux (with systemd)!" end + if (sudo_service_user = args.sudo_service_user) + unless ::Service::System.root? + raise UsageError, + "`brew services` is supported only when running as root!" + end + + unless ::Service::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 + + ::Service::ServicesCli.sudo_service_user = sudo_service_user + end + # Parse arguments. subcommand, formula, custom_plist, = args.named diff --git a/lib/service/formula_wrapper.rb b/lib/service/formula_wrapper.rb index a94951414..f6f2caa8a 100644 --- a/lib/service/formula_wrapper.rb +++ b/lib/service/formula_wrapper.rb @@ -120,6 +120,16 @@ def service_file_present?(opts = { for: false }) end def owner + if System.launchctl? && dest.exist? + require "rexml/document" + + # read the username from the plist file + plist = REXML::Document.new(dest.read) + username_xpath = "/plist/dict/key[text()='UserName']/following-sibling::*[1]" + plist_username = REXML::XPath.first(plist, username_xpath)&.text + + return plist_username if plist_username.present? + end return "root" if boot_path_service_file_present? return System.user if user_path_service_file_present? diff --git a/lib/service/services_cli.rb b/lib/service/services_cli.rb index 23f2dc101..15c7aa514 100644 --- a/lib/service/services_cli.rb +++ b/lib/service/services_cli.rb @@ -6,6 +6,14 @@ module ServicesCli module_function + def sudo_service_user + @sudo_service_user + end + + def sudo_service_user=(sudo_service_user) + @sudo_service_user = sudo_service_user + end + # Binary name. def bin "brew services" @@ -183,6 +191,7 @@ def kill(targets, verbose: false) # protections to avoid users editing root services def take_root_ownership(service) return unless System.root? + return if sudo_service_user root_paths = [] @@ -284,7 +293,22 @@ def install_service_file(service, file) temp = Tempfile.new(service.service_name) temp << if file.blank? - service.service_file.read + contents = service.service_file.read + + if sudo_service_user && System.launchctl? + require "rexml/document" + + # set the username in the new plist file + ohai "Setting username in #{service.service_name} to #{System.user}" + plist = REXML::Document.new(contents) + dict_element = REXML::XPath.first(plist, "/plist/dict/") + dict_element.add_element("key").text = "UserName" + dict_element.add_element("string").text = sudo_service_user + + plist.to_s + else + contents + end else file.read end