chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
// 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())
|
||||
}
|
||||
Reference in New Issue
Block a user