mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 22:14:34 +00:00
feat(skills): add cross-platform install fallback for non-brew environments (#17687)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 3ed4850838
Co-authored-by: mcrolly <60803337+mcrolly@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight
This commit is contained in:
@@ -275,10 +275,15 @@ export async function installSkill(params: SkillInstallRequest): Promise<SkillIn
|
||||
|
||||
const brewExe = hasBinary("brew") ? "brew" : resolveBrewExecutable();
|
||||
if (spec.kind === "brew" && !brewExe) {
|
||||
const formula = spec.formula ?? "this package";
|
||||
const hint =
|
||||
process.platform === "linux"
|
||||
? `Homebrew is not installed. Install it from https://brew.sh or install "${formula}" manually using your system package manager (e.g. apt, dnf, pacman).`
|
||||
: "Homebrew is not installed. Install it from https://brew.sh";
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message: "brew not installed",
|
||||
message: `brew not installed — ${hint}`,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
code: null,
|
||||
@@ -307,7 +312,8 @@ export async function installSkill(params: SkillInstallRequest): Promise<SkillIn
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message: "uv not installed (install via brew)",
|
||||
message:
|
||||
"uv not installed — install manually: https://docs.astral.sh/uv/getting-started/installation/",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
code: null,
|
||||
@@ -350,11 +356,145 @@ export async function installSkill(params: SkillInstallRequest): Promise<SkillIn
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
} else if (hasBinary("apt-get")) {
|
||||
const aptInstallArgv = ["apt-get", "install", "-y", "golang-go"];
|
||||
const aptUpdateArgv = ["apt-get", "update", "-qq"];
|
||||
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
||||
|
||||
if (isRoot) {
|
||||
try {
|
||||
// Best effort: fresh containers often need package indexes populated.
|
||||
await runCommandWithTimeout(aptUpdateArgv, { timeoutMs });
|
||||
} catch {
|
||||
// ignore and continue; install command will return actionable stderr on failure
|
||||
}
|
||||
|
||||
let aptResult;
|
||||
try {
|
||||
aptResult = await runCommandWithTimeout(aptInstallArgv, { timeoutMs });
|
||||
} catch (err) {
|
||||
const stderr = err instanceof Error ? err.message : String(err);
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — automatic install via apt failed. Install manually: https://go.dev/doc/install",
|
||||
stdout: "",
|
||||
stderr,
|
||||
code: null,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
if (aptResult.code !== 0) {
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — automatic install via apt failed. Install manually: https://go.dev/doc/install",
|
||||
stdout: aptResult.stdout.trim(),
|
||||
stderr: aptResult.stderr.trim(),
|
||||
code: aptResult.code,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Check for non-interactive sudo before attempting apt — avoids hanging
|
||||
// in containers or environments where sudo is missing or requires a password.
|
||||
if (!hasBinary("sudo")) {
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — apt-get is available but sudo is not installed. Install manually: https://go.dev/doc/install",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
code: null,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
|
||||
let sudoCheck;
|
||||
try {
|
||||
sudoCheck = await runCommandWithTimeout(["sudo", "-n", "true"], {
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
} catch (err) {
|
||||
const stderr = err instanceof Error ? err.message : String(err);
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — apt-get is available but sudo is not usable (missing or requires a password). Install manually: https://go.dev/doc/install",
|
||||
stdout: "",
|
||||
stderr,
|
||||
code: null,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
if (sudoCheck.code !== 0) {
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — apt-get is available but sudo is not usable (missing or requires a password). Install manually: https://go.dev/doc/install",
|
||||
stdout: sudoCheck.stdout.trim(),
|
||||
stderr: sudoCheck.stderr.trim(),
|
||||
code: sudoCheck.code,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Best effort: fresh containers often need package indexes populated.
|
||||
await runCommandWithTimeout(["sudo", ...aptUpdateArgv], { timeoutMs });
|
||||
} catch {
|
||||
// ignore and continue; install command will return actionable stderr on failure
|
||||
}
|
||||
|
||||
let aptResult;
|
||||
try {
|
||||
aptResult = await runCommandWithTimeout(["sudo", ...aptInstallArgv], {
|
||||
timeoutMs,
|
||||
});
|
||||
} catch (err) {
|
||||
const stderr = err instanceof Error ? err.message : String(err);
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — automatic install via apt failed. Install manually: https://go.dev/doc/install",
|
||||
stdout: "",
|
||||
stderr,
|
||||
code: null,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
|
||||
if (aptResult.code !== 0) {
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message:
|
||||
"go not installed — automatic install via apt failed. Install manually: https://go.dev/doc/install",
|
||||
stdout: aptResult.stdout.trim(),
|
||||
stderr: aptResult.stderr.trim(),
|
||||
code: aptResult.code,
|
||||
},
|
||||
warnings,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return withWarnings(
|
||||
{
|
||||
ok: false,
|
||||
message: "go not installed (install via brew)",
|
||||
message: "go not installed — install manually: https://go.dev/doc/install",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
code: null,
|
||||
|
||||
Reference in New Issue
Block a user