mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
package skills
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
debPackageNameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9+.-]*$`)
|
|
systemLookPath = exec.LookPath
|
|
systemCommandCombinedOutput = runSystemCommandCombinedOutput
|
|
aptSystemPackageAliases = map[string]string{"pip3": "python3-pip", "github-cli": "gh", "go": "golang-go", "golang": "golang-go"}
|
|
errSystemPackageMgrUnavailable = "system package manager unavailable on this runtime"
|
|
)
|
|
|
|
func runSystemCommandCombinedOutput(ctx context.Context, name string, args ...string) ([]byte, error) {
|
|
return exec.CommandContext(ctx, name, args...).CombinedOutput()
|
|
}
|
|
|
|
func installSystemPackage(ctx context.Context, requested string) (bool, string) {
|
|
if IsAlpineRuntime() {
|
|
return apkViaHelper(ctx, "install", requested)
|
|
}
|
|
pkg, err := resolveDebianPackageName(requested)
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
if _, err := systemLookPath("apt-get"); err != nil {
|
|
return false, errSystemPackageMgrUnavailable
|
|
}
|
|
if ok, msg := runAptCommand(ctx, "install", pkg); !ok {
|
|
return false, msg
|
|
}
|
|
if err := addSystemPackageRecord(requested, pkg, "apt"); err != nil {
|
|
slog.Warn("skills: system package record add failed", "package", requested, "resolved", pkg, "error", err)
|
|
return false, fmt.Sprintf("package installed but package record update failed: %v", err)
|
|
}
|
|
return true, ""
|
|
}
|
|
|
|
func uninstallSystemPackage(ctx context.Context, requested string) (bool, string) {
|
|
if IsAlpineRuntime() {
|
|
return apkViaHelper(ctx, "uninstall", requested)
|
|
}
|
|
pkg, err := resolveDebianPackageName(requested)
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
if _, err := systemLookPath("apt-get"); err != nil {
|
|
return false, errSystemPackageMgrUnavailable
|
|
}
|
|
if ok, msg := runAptCommand(ctx, "remove", pkg); !ok {
|
|
return false, msg
|
|
}
|
|
if err := removeSystemPackageRecord(requested, pkg, "apt"); err != nil {
|
|
slog.Warn("skills: system package record remove failed", "package", requested, "resolved", pkg, "error", err)
|
|
return false, fmt.Sprintf("package removed but package record update failed: %v", err)
|
|
}
|
|
return true, ""
|
|
}
|
|
|
|
func resolveDebianPackageName(requested string) (string, error) {
|
|
pkg := strings.ToLower(strings.TrimSpace(requested))
|
|
if alias, ok := aptSystemPackageAliases[pkg]; ok {
|
|
pkg = alias
|
|
}
|
|
if !debPackageNameRE.MatchString(pkg) {
|
|
return "", fmt.Errorf("invalid Debian package name: %s", requested)
|
|
}
|
|
return pkg, nil
|
|
}
|
|
|
|
func runAptCommand(ctx context.Context, action, pkg string) (bool, string) {
|
|
args := []string{"-n", "env", "DEBIAN_FRONTEND=noninteractive", "apt-get"}
|
|
switch action {
|
|
case "install":
|
|
args = append(args, "install", "-y", "--no-install-recommends", pkg)
|
|
case "remove":
|
|
args = append(args, "remove", "-y", pkg)
|
|
default:
|
|
return false, "unsupported apt action"
|
|
}
|
|
out, err := systemCommandCombinedOutput(ctx, "sudo", args...)
|
|
if err != nil {
|
|
msg := strings.TrimSpace(string(out))
|
|
if msg == "" {
|
|
msg = err.Error()
|
|
} else {
|
|
msg = fmt.Sprintf("%s: %v", msg, err)
|
|
}
|
|
return false, msg
|
|
}
|
|
return true, ""
|
|
}
|