Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

344 lines
7.6 KiB
Go

// codebase-memory-mcp — Go installer wrapper.
//
// On first run, downloads the pre-built binary for the current platform from
// GitHub Releases, caches it, and replaces the current process with it.
// Subsequent runs skip directly to exec.
//
// Install:
//
// go install github.com/DeusData/codebase-memory-mcp/pkg/go/cmd/codebase-memory-mcp@latest
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
)
const (
repo = "DeusData/codebase-memory-mcp"
version = "0.8.1"
)
func main() {
bin, err := ensureBinary()
if err != nil {
fmt.Fprintf(os.Stderr, "codebase-memory-mcp: %v\n", err)
os.Exit(1)
}
if err := execBinary(bin, os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "codebase-memory-mcp: %v\n", err)
os.Exit(1)
}
}
func ensureBinary() (string, error) {
bin := binPath()
if _, err := os.Stat(bin); err == nil {
return bin, nil
}
if err := download(bin); err != nil {
return "", err
}
return bin, nil
}
func binPath() string {
name := "codebase-memory-mcp"
if runtime.GOOS == "windows" {
name += ".exe"
}
return filepath.Join(cacheDir(), version, name)
}
func cacheDir() string {
if d := os.Getenv("CBM_CACHE_DIR"); d != "" {
return d
}
switch runtime.GOOS {
case "windows":
if d := os.Getenv("LOCALAPPDATA"); d != "" {
return filepath.Join(d, "codebase-memory-mcp")
}
case "darwin":
if home, err := os.UserHomeDir(); err == nil {
return filepath.Join(home, "Library", "Caches", "codebase-memory-mcp")
}
}
if d := os.Getenv("XDG_CACHE_HOME"); d != "" {
return filepath.Join(d, "codebase-memory-mcp")
}
if home, err := os.UserHomeDir(); err == nil {
return filepath.Join(home, ".cache", "codebase-memory-mcp")
}
return filepath.Join(os.TempDir(), "codebase-memory-mcp")
}
func goos() string {
switch runtime.GOOS {
case "darwin":
return "darwin"
case "linux":
return "linux"
case "windows":
return "windows"
default:
return runtime.GOOS
}
}
func goarch() string {
switch runtime.GOARCH {
case "amd64":
return "amd64"
case "arm64":
return "arm64"
default:
return runtime.GOARCH
}
}
func download(dest string) error {
platform := goos()
arch := goarch()
ext := "tar.gz"
if platform == "windows" {
ext = "zip"
}
archive := fmt.Sprintf("codebase-memory-mcp-%s-%s.%s", platform, arch, ext)
url := fmt.Sprintf("https://github.com/%s/releases/download/v%s/%s", repo, version, archive)
checksumURL := fmt.Sprintf("https://github.com/%s/releases/download/v%s/checksums.txt", repo, version)
fmt.Fprintf(os.Stderr, "codebase-memory-mcp: downloading v%s for %s/%s...\n", version, platform, arch)
tmp, err := os.MkdirTemp("", "cbm-install-*")
if err != nil {
return err
}
defer os.RemoveAll(tmp)
archivePath := filepath.Join(tmp, "cbm."+ext)
if err := httpGet(url, archivePath); err != nil {
return fmt.Errorf("download failed: %w", err)
}
// Verify checksum if available (non-fatal if checksums.txt unreachable)
if checksums, err := fetchChecksums(checksumURL); err == nil {
if expected, ok := checksums[archive]; ok {
if err := verifyChecksum(archivePath, expected); err != nil {
return err
}
}
}
binName := "codebase-memory-mcp"
if platform == "windows" {
binName += ".exe"
}
if ext == "tar.gz" {
if err := extractTarGz(archivePath, tmp, binName); err != nil {
return fmt.Errorf("extraction failed: %w", err)
}
} else {
if err := extractZip(archivePath, tmp, binName); err != nil {
return fmt.Errorf("extraction failed: %w", err)
}
}
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return fmt.Errorf("could not create cache dir: %w", err)
}
if err := copyFile(filepath.Join(tmp, binName), dest); err != nil {
return fmt.Errorf("could not install binary: %w", err)
}
if err := os.Chmod(dest, 0755); err != nil {
return fmt.Errorf("could not set permissions: %w", err)
}
return nil
}
// validateURLScheme rejects non-https URLs before any fetch (defense-in-depth).
func validateURLScheme(rawURL string) error {
if !strings.HasPrefix(rawURL, "https://") {
return fmt.Errorf("refusing non-https URL: %s", rawURL)
}
return nil
}
// httpsOnlyClient returns an HTTP client that rejects non-HTTPS redirects.
var httpsOnlyClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return fmt.Errorf("refusing non-https redirect to %s", req.URL)
}
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}
return nil
},
}
func httpGet(rawURL, dest string) error {
if err := validateURLScheme(rawURL); err != nil {
return err
}
resp, err := httpsOnlyClient.Get(rawURL) //nolint:gosec
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d for %s", resp.StatusCode, rawURL)
}
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func fetchChecksums(url string) (map[string]string, error) {
if err := validateURLScheme(url); err != nil {
return nil, err
}
resp, err := httpsOnlyClient.Get(url) //nolint:gosec
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
result := make(map[string]string)
for _, line := range strings.Split(string(body), "\n") {
parts := strings.Fields(line)
if len(parts) == 2 {
result[parts[1]] = parts[0]
}
}
return result, nil
}
func verifyChecksum(path, expected string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
actual := hex.EncodeToString(h.Sum(nil))
if actual != expected {
return fmt.Errorf("checksum mismatch: expected %s, got %s", expected, actual)
}
return nil
}
func extractTarGz(archivePath, destDir, targetFile string) error {
f, err := os.Open(archivePath)
if err != nil {
return err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if filepath.Base(hdr.Name) == targetFile {
out, err := os.Create(filepath.Join(destDir, targetFile))
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, tr) //nolint:gosec
return err
}
}
return fmt.Errorf("%s not found in archive", targetFile)
}
func extractZip(archivePath, destDir, targetFile string) error {
r, err := zip.OpenReader(archivePath)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
if filepath.Base(f.Name) == targetFile {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
out, err := os.Create(filepath.Join(destDir, targetFile))
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, rc) //nolint:gosec
return err
}
}
return fmt.Errorf("%s not found in archive", targetFile)
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
func execBinary(bin string, args []string) error {
if runtime.GOOS == "windows" {
cmd := exec.Command(bin, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
return syscall.Exec(bin, append([]string{bin}, args...), os.Environ())
}