Files
1Panel/core/utils/cmd/cmd.go
Sanjay Santhanam b06bc0a455 fix(cmd): use exec.LookPath in Which() so detection works without external 'which' (#12651)
cmd.Which() previously shelled out to `which <name>` to determine whether
a binary was on PATH. On distributions that do not ship a `which` package
by default — Arch Linux is the canonical example, but the same applies to
several minimal container images — `which` itself is missing, so every
call to Which() returned false and 1Panel reported core dependencies
(notably Docker) as 'not installed' even when they were running normally.

Switch the primary path to Go's exec.LookPath, which uses the process
PATH directly and has no external dependency. The original shell-out is
preserved as a fallback so any environment where the agent's PATH
differs from the user's interactive shell PATH (the original reason the
shell-out existed) keeps working unchanged.

Both copies are updated (agent/utils/cmd/cmd.go and core/utils/cmd/cmd.go)
and a small unit test is added to each package to guard the regression.

Fixes #12605

Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
2026-05-06 10:47:02 +08:00

69 lines
1.7 KiB
Go

package cmd
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
)
func SudoHandleCmd() string {
cmd := exec.Command("sudo", "-n", "ls")
if err := cmd.Run(); err == nil {
return "sudo "
}
return ""
}
func Which(name string) bool {
// Prefer Go's built-in PATH lookup so we don't depend on an external
// `which` binary, which is not installed by default on minimal
// distributions (e.g. Arch Linux, some Alpine images, slim containers).
// See 1Panel-dev/1Panel#12605.
if _, err := exec.LookPath(name); err == nil {
return true
}
// Fall back to shelling out for environments where PATH inside the
// agent process differs from the user's interactive shell PATH (the
// previous behaviour, preserved for compatibility).
stdout, err := RunDefaultWithStdoutBashCf("which %s", name)
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) {
return false
}
return true
}
func ExecWithStreamOutput(command string, outputCallback func(string)) error {
cmd := exec.Command("bash", "-c", command)
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to get stdout: %w", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to get stderr: %w", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start command: %w", err)
}
go streamReader(stdout, outputCallback)
go streamReader(stderr, outputCallback)
if err := cmd.Wait(); err != nil {
return fmt.Errorf("command finished with error: %w", err)
}
return nil
}
func streamReader(reader io.ReadCloser, callback func(string)) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
callback(scanner.Text())
}
}