Fix PKG installer environment

This commit solves an issue where the environment handed to
`/usr/sbin/installer` is not the same as the environment used by the
graphical PKG installer.

This is evident in some post-install scripts, e. g. the
`component-10.pkg/Scripts/postinstall` script in the `dymo-label`
cask. The code says:

```
USER_ID=`id -u ${USER}`

launchctl bootstrap gui/$USER_ID /Library/LaunchAgents/com.dymo.dls.webservice.plist
```

The graphical installer will export e. g. `USER=alice`, and
everything works as intended.

However, `brew cask install` does not override `sudo`’s default,
which is `USER=ROOT`. This violates the assumptions in the script.

This commit fixes the issue by configuring `sudo` to override the
following environment variables with the proper user name:

- `LOGNAME`
- `USER`
- `USERNAME`
This commit is contained in:
Claudia 2018-06-01 23:26:12 +02:00
parent 9fff177844
commit 3dbb735f3c
No known key found for this signature in database
GPG Key ID: 246AC3C0F10BE51F
6 changed files with 100 additions and 10 deletions

View File

@ -50,7 +50,13 @@ module Hbc
end end
with_choices_file do |choices_path| with_choices_file do |choices_path|
args << "-applyChoiceChangesXML" << choices_path if choices_path args << "-applyChoiceChangesXML" << choices_path if choices_path
command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true) logged_in_user = Utils.current_user
env = {
"LOGNAME" => logged_in_user,
"USER" => logged_in_user,
"USERNAME" => logged_in_user,
}
command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true, env: env)
end end
end end

View File

@ -37,7 +37,7 @@ module Hbc
result result
end end
def initialize(executable, args: [], sudo: false, input: [], print_stdout: false, print_stderr: true, must_succeed: false, path: ENV["PATH"], **options) def initialize(executable, args: [], sudo: false, input: [], print_stdout: false, print_stderr: true, must_succeed: false, env: {}, **options)
@executable = executable @executable = executable
@args = args @args = args
@sudo = sudo @sudo = sudo
@ -47,7 +47,11 @@ module Hbc
@must_succeed = must_succeed @must_succeed = must_succeed
options.extend(HashValidator).assert_valid_keys(:chdir) options.extend(HashValidator).assert_valid_keys(:chdir)
@options = options @options = options
@path = path @env = { "PATH" => ENV["PATH"] }.merge(env)
@env.keys.grep_v(/^[\w&&\D]\w*$/) do |name|
raise ArgumentError, "Invalid variable name: '#{name}'"
end
end end
def command def command
@ -56,14 +60,22 @@ module Hbc
private private
attr_reader :executable, :args, :input, :options, :processed_output, :processed_status, :path attr_reader :executable, :args, :input, :options, :processed_output, :processed_status, :env
attr_predicate :sudo?, :print_stdout?, :print_stderr?, :must_succeed? attr_predicate :sudo?, :print_stdout?, :print_stderr?, :must_succeed?
def sudo_prefix def sudo_prefix
return [] unless sudo? return [] unless sudo?
askpass_flags = ENV.key?("SUDO_ASKPASS") ? ["-A"] : [] askpass_flags = ENV.key?("SUDO_ASKPASS") ? ["-A"] : []
["/usr/bin/sudo", *askpass_flags, "-E", "--"] prefix = ["/usr/bin/sudo", *askpass_flags, "-E"]
env.each do |name, value|
sanitized_name = Shellwords.escape(name)
sanitized_value = Shellwords.escape(value)
prefix << "#{sanitized_name}=#{sanitized_value}"
end
prefix << "--"
end end
def assert_success def assert_success
@ -85,7 +97,7 @@ module Hbc
executable, *args = expanded_command executable, *args = expanded_command
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr = raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
Open3.popen3({ "PATH" => path }, [executable, executable], *args, **options) Open3.popen3(env, [executable, executable], *args, **options)
write_input_to(raw_stdin) write_input_to(raw_stdin)
raw_stdin.close_write raw_stdin.close_write

View File

@ -15,6 +15,11 @@ describe Hbc::Artifact::Pkg, :cask do
args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/"], args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/"],
sudo: true, sudo: true,
print_stdout: true, print_stdout: true,
env: {
"LOGNAME" => ENV["USER"],
"USER" => ENV["USER"],
"USERNAME" => ENV["USER"],
},
) )
pkg.install_phase(command: fake_system_command) pkg.install_phase(command: fake_system_command)
@ -55,6 +60,11 @@ describe Hbc::Artifact::Pkg, :cask do
args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/", "-applyChoiceChangesXML", cask.staged_path.join("/tmp/choices.xml")], args: ["-pkg", cask.staged_path.join("MyFancyPkg", "Fancy.pkg"), "-target", "/", "-applyChoiceChangesXML", cask.staged_path.join("/tmp/choices.xml")],
sudo: true, sudo: true,
print_stdout: true, print_stdout: true,
env: {
"LOGNAME" => ENV["USER"],
"USER" => ENV["USER"],
"USERNAME" => ENV["USER"],
},
) )
pkg.install_phase(command: fake_system_command) pkg.install_phase(command: fake_system_command)

View File

@ -1,4 +1,59 @@
describe Hbc::SystemCommand, :cask do describe Hbc::SystemCommand, :cask do
describe "#initialize" do
let(:env_args) { ["bash", "-c", 'printf "%s" "${A?}" "${B?}" "${C?}"'] }
describe "given some environment variables" do
subject {
described_class.new(
"env",
args: env_args,
env: { "A" => "1", "B" => "2", "C" => "3" },
must_succeed: true,
)
}
its("run!.stdout") { is_expected.to eq("123") }
describe "the resulting command line" do
it "does not include the given variables" do
expect(Open3)
.to receive(:popen3)
.with(a_hash_including("PATH"), ["env"] * 2, *env_args, {})
.and_call_original
subject.run!
end
end
end
describe "given some environment variables and sudo: true" do
subject {
described_class.new(
"env",
args: env_args,
env: { "A" => "1", "B" => "2", "C" => "3" },
must_succeed: true,
sudo: true,
)
}
describe "the resulting command line" do
it "includes the given variables explicitly" do
expect(Open3)
.to receive(:popen3)
.with(an_instance_of(Hash), ["/usr/bin/sudo"] * 2,
"-E", a_string_starting_with("PATH="),
"A=1", "B=2", "C=3", "--", "env", *env_args, {})
.and_wrap_original do |original_popen3, *_, &block|
original_popen3.call("/usr/bin/true", &block)
end
subject.run!
end
end
end
end
describe "when the exit code is 0" do describe "when the exit code is 0" do
describe "its result" do describe "its result" do
subject { described_class.run("/usr/bin/true") } subject { described_class.run("/usr/bin/true") }
@ -134,4 +189,11 @@ describe Hbc::SystemCommand, :cask do
}.to be_a_success }.to be_a_success
end end
end end
describe "given an invalid variable name" do
it "raises an ArgumentError" do
expect { described_class.run("true", env: { "1ABC" => true }) }
.to raise_error(ArgumentError, /variable name/)
end
end
end end

View File

@ -1,5 +1,5 @@
def sudo(*args) def sudo(*args)
%w[/usr/bin/sudo -E --] + args.flatten ["/usr/bin/sudo", "-E", "PATH=#{ENV["PATH"]}", "--"] + args.flatten
end end
module Hbc module Hbc

View File

@ -80,7 +80,7 @@ shared_examples Hbc::Staged do
allow(staged).to receive(:Pathname).and_return(fake_pathname) allow(staged).to receive(:Pathname).and_return(fake_pathname)
Hbc::FakeSystemCommand.expects_command( Hbc::FakeSystemCommand.expects_command(
["/usr/bin/sudo", "-E", "--", "/usr/sbin/chown", "-R", "--", "fake_user:staff", fake_pathname], ["/usr/bin/sudo", "-E", "PATH=#{ENV["PATH"]}", "--", "/usr/sbin/chown", "-R", "--", "fake_user:staff", fake_pathname],
) )
staged.set_ownership(fake_pathname.to_s) staged.set_ownership(fake_pathname.to_s)
@ -93,7 +93,7 @@ shared_examples Hbc::Staged do
allow(staged).to receive(:Pathname).and_return(fake_pathname) allow(staged).to receive(:Pathname).and_return(fake_pathname)
Hbc::FakeSystemCommand.expects_command( Hbc::FakeSystemCommand.expects_command(
["/usr/bin/sudo", "-E", "--", "/usr/sbin/chown", "-R", "--", "fake_user:staff", fake_pathname, fake_pathname], ["/usr/bin/sudo", "-E", "PATH=#{ENV["PATH"]}", "--", "/usr/sbin/chown", "-R", "--", "fake_user:staff", fake_pathname, fake_pathname],
) )
staged.set_ownership([fake_pathname.to_s, fake_pathname.to_s]) staged.set_ownership([fake_pathname.to_s, fake_pathname.to_s])
@ -105,7 +105,7 @@ shared_examples Hbc::Staged do
allow(staged).to receive(:Pathname).and_return(fake_pathname) allow(staged).to receive(:Pathname).and_return(fake_pathname)
Hbc::FakeSystemCommand.expects_command( Hbc::FakeSystemCommand.expects_command(
["/usr/bin/sudo", "-E", "--", "/usr/sbin/chown", "-R", "--", "other_user:other_group", fake_pathname], ["/usr/bin/sudo", "-E", "PATH=#{ENV["PATH"]}", "--", "/usr/sbin/chown", "-R", "--", "other_user:other_group", fake_pathname],
) )
staged.set_ownership(fake_pathname.to_s, user: "other_user", group: "other_group") staged.set_ownership(fake_pathname.to_s, user: "other_user", group: "other_group")