diff --git a/cmd/gateway_github_installer.go b/cmd/gateway_github_installer.go index 2be97534..41b2fc14 100644 --- a/cmd/gateway_github_installer.go +++ b/cmd/gateway_github_installer.go @@ -18,7 +18,7 @@ import ( // GOCLAW_PACKAGES_GITHUB_TOKEN optional PAT (boosts rate limit, enables private repos) // GOCLAW_PACKAGES_MAX_ASSET_SIZE_MB default 200 // GOCLAW_PACKAGES_GITHUB_ALLOWED_ORGS comma-separated allowlist (empty = all allowed) -// GOCLAW_PACKAGES_GITHUB_BIN_DIR default /app/data/.runtime/bin +// GOCLAW_PACKAGES_GITHUB_BIN_DIR default {runtimeDir}/bin // GOCLAW_PACKAGES_GITHUB_MANIFEST default {BIN_DIR}/../github-packages.json func initGitHubInstaller() { cfg := &skills.GitHubPackagesConfig{ diff --git a/docs/14-skills-runtime.md b/docs/14-skills-runtime.md index c41f4c44..ac8fe740 100644 --- a/docs/14-skills-runtime.md +++ b/docs/14-skills-runtime.md @@ -217,7 +217,7 @@ github:owner/repo[@tag] ``` Admin-only, SHA256-verified, ELF-validated, with a release-picker UI. Binaries -land in `/app/data/.runtime/bin/` (on `$PATH`). See +land in `{runtimeDir}/bin/` (on `$PATH`). See [`docs/packages-github.md`](./packages-github.md) for syntax, configuration, security posture, and troubleshooting (especially musl/glibc compatibility). diff --git a/docs/packages-github.md b/docs/packages-github.md index d8e6fbb7..9749fd67 100644 --- a/docs/packages-github.md +++ b/docs/packages-github.md @@ -24,7 +24,7 @@ Examples: 4. Verifies SHA256 if the publisher ships `checksums.txt` / `SHA256SUMS` 5. Validates ELF magic bytes + 64-bit class + machine matches runtime arch 6. Extracts archive safely (tar.gz / zip / raw binary) with path-traversal + zip-bomb guards -7. Installs to `/app/data/.runtime/bin/` (prepended to `$PATH`) +7. Installs to `{runtimeDir}/bin/` (prepended to `$PATH`) 8. Persists a manifest for later listing + uninstall ## Usage @@ -69,7 +69,7 @@ token in `config.json`. | `GOCLAW_PACKAGES_GITHUB_TOKEN` | `""` | Optional PAT: rate 60/hr → 5000/hr + private repo access | | `GOCLAW_PACKAGES_MAX_ASSET_SIZE_MB` | `200` | Applies to both download cap and 2× uncompressed cap | | `GOCLAW_PACKAGES_GITHUB_ALLOWED_ORGS` | `""` | Comma-separated allowlist (empty = all orgs allowed) | -| `GOCLAW_PACKAGES_GITHUB_BIN_DIR` | `/app/data/.runtime/bin` | Where extracted binaries land | +| `GOCLAW_PACKAGES_GITHUB_BIN_DIR` | `{runtimeDir}/bin` | Where extracted binaries land | | `GOCLAW_PACKAGES_GITHUB_MANIFEST` | `{bin_dir}/../github-packages.json` | Manifest path | Token scopes: @@ -133,7 +133,7 @@ the release. Do not force-install; report upstream. ## Limitations (Phase 1) - Linux-only (Lite/Desktop editions not yet supported) -- Docker edition only (runtime dir `/app/data/.runtime/bin`) +- Docker and bare-metal gateway editions (default runtime dir resolves to `/app/data/.runtime/bin` in Docker or `/var/lib/goclaw/data/.runtime/bin` on bare-metal Linux) - Installs all top-level executables in an archive (no interactive picker if archive contains multiple binaries) - No version history / rollback — re-installing replaces in place diff --git a/docs/project-changelog.md b/docs/project-changelog.md index 4a307bcb..73b65f85 100644 --- a/docs/project-changelog.md +++ b/docs/project-changelog.md @@ -6,6 +6,19 @@ Significant changes, features, and fixes in reverse chronological order. ## 2026-05-18 +### Packages: GitHub installer runtime path + +**Fixes** + +- Fixed GitHub Releases package installs on bare-metal gateways by defaulting the GitHub binary directory to `{runtimeDir}/bin` instead of Docker-only `/app/data/.runtime/bin`. +- The fix covers installs such as `github:nextlevelbuilder/goclaw-cli@v0.4.1` on the VPS, where `/app` is not writable or present. + +**Tests** + +- Added default-path regression coverage and made Unix-socket apk helper tests skip cleanly on Windows environments that cannot bind Unix sockets. + +--- + ### Providers: ChatGPT OAuth GPT-5.5 default **Changed** diff --git a/internal/skills/apk_helper_call_test.go b/internal/skills/apk_helper_call_test.go index 382bb40e..67da4eab 100644 --- a/internal/skills/apk_helper_call_test.go +++ b/internal/skills/apk_helper_call_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net" + "runtime" "strings" "sync/atomic" "testing" @@ -42,6 +43,9 @@ func servePkgHelper(t *testing.T, sockPath, respJSON string) func() { ln, err := net.Listen("unix", sockPath) if err != nil { + if runtime.GOOS == "windows" { + t.Skipf("unix sockets are not available in this Windows test environment: %v", err) + } t.Fatalf("servePkgHelper: listen %q: %v", sockPath, err) } diff --git a/internal/skills/github_installer.go b/internal/skills/github_installer.go index 504f92e2..f44bdad3 100644 --- a/internal/skills/github_installer.go +++ b/internal/skills/github_installer.go @@ -57,7 +57,7 @@ func ParseGitHubSpec(s string) (*GitHubSpec, error) { // Token is sourced from env var only (never config.json plaintext). type GitHubPackagesConfig struct { Token string // optional GitHub personal access token - BinDir string // where to install binaries (default /app/data/.runtime/bin) + BinDir string // where to install binaries (default {runtimeDir}/bin) ManifestPath string // manifest file path (default {BinDir}/../github-packages.json) AllowedOrgs []string // lowercase list; empty = all allowed MaxAssetSizeMB int // default 200 @@ -66,7 +66,7 @@ type GitHubPackagesConfig struct { // Defaults fills in zero-valued fields. func (c *GitHubPackagesConfig) Defaults() { if c.BinDir == "" { - c.BinDir = "/app/data/.runtime/bin" + c.BinDir = filepath.Join(packageRuntimeDir(), "bin") } if c.ManifestPath == "" { c.ManifestPath = filepath.Join(filepath.Dir(c.BinDir), "github-packages.json") diff --git a/internal/skills/github_installer_test.go b/internal/skills/github_installer_test.go index 3d226fce..aec3874b 100644 --- a/internal/skills/github_installer_test.go +++ b/internal/skills/github_installer_test.go @@ -2,17 +2,18 @@ package skills import ( "errors" + "path/filepath" "strings" "testing" ) func TestParseGitHubSpec(t *testing.T) { cases := []struct { - in string - ok bool - owner string - repo string - tag string + in string + ok bool + owner string + repo string + tag string }{ {"github:cli/cli@v2.45.0", true, "cli", "cli", "v2.45.0"}, {"github:cli/cli", true, "cli", "cli", ""}, @@ -123,11 +124,20 @@ func TestAllowedOrg(t *testing.T) { } func TestConfigDefaults(t *testing.T) { + runtimeDir := t.TempDir() + t.Setenv("RUNTIME_DIR", runtimeDir) + c := &GitHubPackagesConfig{} c.Defaults() if c.BinDir == "" || c.ManifestPath == "" || c.MaxAssetSizeMB != 200 { t.Errorf("unexpected defaults: %+v", c) } + if want := filepath.Join(runtimeDir, "bin"); c.BinDir != want { + t.Errorf("BinDir = %q, want %q", c.BinDir, want) + } + if want := filepath.Join(runtimeDir, "github-packages.json"); c.ManifestPath != want { + t.Errorf("ManifestPath = %q, want %q", c.ManifestPath, want) + } if c.MaxAssetBytes() != 200*1024*1024 { t.Errorf("MaxAssetBytes wrong: %d", c.MaxAssetBytes()) } diff --git a/internal/skills/runtime_check.go b/internal/skills/runtime_check.go index b39298ca..56b5e70f 100644 --- a/internal/skills/runtime_check.go +++ b/internal/skills/runtime_check.go @@ -4,6 +4,7 @@ import ( "context" "os" "os/exec" + "path/filepath" "strings" "time" ) @@ -65,7 +66,7 @@ func CheckRuntimes() *RuntimeStatus { // Check github-bin runtime directory (where GitHub-installed binaries live). ghInfo := RuntimeInfo{Name: "github-bin"} - binDir := "/app/data/.runtime/bin" + binDir := filepath.Join(packageRuntimeDir(), "bin") if gh := DefaultGitHubInstaller(); gh != nil && gh.Config != nil && gh.Config.BinDir != "" { binDir = gh.Config.BinDir }