diff --git a/main.go b/main.go index 4232bb5..71642a6 100644 --- a/main.go +++ b/main.go @@ -16,10 +16,12 @@ import ( "net/url" "os" "os/exec" + "os/user" "path/filepath" "regexp" "runtime" "strings" + "sync" "golang.org/x/net/idna" ) @@ -345,3 +347,18 @@ func binaryExists(name string) bool { _, err := exec.LookPath(name) return err == nil } + +var sudoWarningOnce sync.Once + +func commandWithSudo(cmd ...string) *exec.Cmd { + if u, err := user.Current(); err == nil && u.Uid == "0" { + return exec.Command(cmd[0], cmd[1:]...) + } + if !binaryExists("sudo") { + sudoWarningOnce.Do(func() { + log.Println(`Warning: "sudo" is not available, and mkcert is not running as root. The (un)install operation might fail. ⚠️`) + }) + return exec.Command(cmd[0], cmd[1:]...) + } + return exec.Command("sudo", append([]string{"--prompt=Sudo password:", "--"}, cmd...)...) +} diff --git a/truststore_darwin.go b/truststore_darwin.go index 1ad78f4..b6982ca 100644 --- a/truststore_darwin.go +++ b/truststore_darwin.go @@ -10,7 +10,6 @@ import ( "io/ioutil" "log" "os" - "os/exec" "path/filepath" "howett.net/plist" @@ -51,7 +50,7 @@ var trustSettingsData = []byte(` `) func (m *mkcert) installPlatform() bool { - cmd := exec.Command("sudo", "security", "add-trusted-cert", "-d", "-k", "/Library/Keychains/System.keychain", filepath.Join(m.CAROOT, rootName)) + cmd := commandWithSudo("security", "add-trusted-cert", "-d", "-k", "/Library/Keychains/System.keychain", filepath.Join(m.CAROOT, rootName)) out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "security add-trusted-cert", out) @@ -62,7 +61,7 @@ func (m *mkcert) installPlatform() bool { fatalIfErr(err, "failed to create temp file") defer os.Remove(plistFile.Name()) - cmd = exec.Command("sudo", "security", "trust-settings-export", "-d", plistFile.Name()) + cmd = commandWithSudo("security", "trust-settings-export", "-d", plistFile.Name()) out, err = cmd.CombinedOutput() fatalIfCmdErr(err, "security trust-settings-export", out) @@ -96,7 +95,7 @@ func (m *mkcert) installPlatform() bool { err = ioutil.WriteFile(plistFile.Name(), plistData, 0600) fatalIfErr(err, "failed to write trust settings") - cmd = exec.Command("sudo", "security", "trust-settings-import", "-d", plistFile.Name()) + cmd = commandWithSudo("security", "trust-settings-import", "-d", plistFile.Name()) out, err = cmd.CombinedOutput() fatalIfCmdErr(err, "security trust-settings-import", out) @@ -104,7 +103,7 @@ func (m *mkcert) installPlatform() bool { } func (m *mkcert) uninstallPlatform() bool { - cmd := exec.Command("sudo", "security", "remove-trusted-cert", "-d", filepath.Join(m.CAROOT, rootName)) + cmd := commandWithSudo("security", "remove-trusted-cert", "-d", filepath.Join(m.CAROOT, rootName)) out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "security remove-trusted-cert", out) diff --git a/truststore_java.go b/truststore_java.go index 75b46ba..410e453 100644 --- a/truststore_java.go +++ b/truststore_java.go @@ -106,12 +106,12 @@ func (m *mkcert) uninstallJava() { } // execKeytool will execute a "keytool" command and if needed re-execute -// the command wrapped in 'sudo' to work around file permissions. +// the command with commandWithSudo to work around file permissions. func (m *mkcert) execKeytool(cmd *exec.Cmd) ([]byte, error) { out, err := cmd.CombinedOutput() if err != nil && bytes.Contains(out, []byte("java.io.FileNotFoundException")) && runtime.GOOS != "windows" { origArgs := cmd.Args[1:] - cmd = exec.Command("sudo", keytoolPath) + cmd = commandWithSudo(keytoolPath) cmd.Args = append(cmd.Args, origArgs...) cmd.Env = []string{ "JAVA_HOME=" + javaHome, diff --git a/truststore_linux.go b/truststore_linux.go index 3e42f74..65c2f1a 100644 --- a/truststore_linux.go +++ b/truststore_linux.go @@ -10,7 +10,6 @@ import ( "io/ioutil" "log" "os" - "os/exec" "path/filepath" "strings" ) @@ -65,12 +64,12 @@ func (m *mkcert) installPlatform() bool { cert, err := ioutil.ReadFile(filepath.Join(m.CAROOT, rootName)) fatalIfErr(err, "failed to read root certificate") - cmd := CommandWithSudo("tee", m.systemTrustFilename()) + cmd := commandWithSudo("tee", m.systemTrustFilename()) cmd.Stdin = bytes.NewReader(cert) out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "tee", out) - cmd = CommandWithSudo(SystemTrustCommand...) + cmd = commandWithSudo(SystemTrustCommand...) out, err = cmd.CombinedOutput() fatalIfCmdErr(err, strings.Join(SystemTrustCommand, " "), out) @@ -82,28 +81,21 @@ func (m *mkcert) uninstallPlatform() bool { return false } - cmd := CommandWithSudo("rm", "-f", m.systemTrustFilename()) + cmd := commandWithSudo("rm", "-f", m.systemTrustFilename()) out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "rm", out) // We used to install under non-unique filenames. legacyFilename := fmt.Sprintf(SystemTrustFilename, "mkcert-rootCA") if pathExists(legacyFilename) { - cmd := CommandWithSudo("rm", "-f", legacyFilename) + cmd := commandWithSudo("rm", "-f", legacyFilename) out, err := cmd.CombinedOutput() fatalIfCmdErr(err, "rm (legacy filename)", out) } - cmd = CommandWithSudo(SystemTrustCommand...) + cmd = commandWithSudo(SystemTrustCommand...) out, err = cmd.CombinedOutput() fatalIfCmdErr(err, strings.Join(SystemTrustCommand, " "), out) return true } - -func CommandWithSudo(cmd ...string) *exec.Cmd { - if !binaryExists("sudo") { - return exec.Command(cmd[0], cmd[1:]...) - } - return exec.Command("sudo", append([]string{"--"}, cmd...)...) -}