Necropolis v1 release

This commit is contained in:
2026-07-07 04:37:29 +01:00
commit cf9c51a3df
69 changed files with 15056 additions and 0 deletions
+399
View File
@@ -0,0 +1,399 @@
package core
import (
"archive/zip"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
func findGo() string {
if p, err := exec.LookPath("go"); err == nil {
return p
}
if goroot := os.Getenv("GOROOT"); goroot != "" {
candidate := filepath.Join(goroot, "bin", "go")
if fileExists(candidate) {
return candidate
}
}
goexe := "go"
if runtime.GOOS == "windows" {
goexe = "go.exe"
}
for _, p := range []string{
"/usr/local/go/bin/go",
filepath.Join(os.Getenv("HOME"), ".local", "go", "bin", goexe),
"/tmp/go/bin/" + goexe,
"/usr/lib/go/bin/go",
`C:\Go\bin\go.exe`,
filepath.Join(os.Getenv("ProgramFiles"), "Go", "bin", "go.exe"),
filepath.Join(os.Getenv("LocalAppData"), "go", "bin", goexe),
filepath.Join(os.Getenv("HOME"), "sdk", "go", "bin", goexe),
filepath.Join(os.TempDir(), "go", "bin", goexe),
} {
if fileExists(p) {
return p
}
}
return goexe
}
type goFile struct {
Filename string `json:"filename"`
OS string `json:"os"`
Arch string `json:"arch"`
SHA256 string `json:"sha256"`
}
type goVersion struct {
Version string `json:"version"`
Stable bool `json:"stable"`
Files []goFile `json:"files"`
}
func ensureGo() (string, error) {
if p, err := exec.LookPath("go"); err == nil {
return p, nil
}
if goroot := os.Getenv("GOROOT"); goroot != "" {
candidate := filepath.Join(goroot, "bin", "go")
if fileExists(candidate) {
return candidate, nil
}
}
if p := findGo(); fileExists(p) {
return p, nil
}
log.Printf("[*] Go not found, installing...")
osName := runtime.GOOS
archName := runtime.GOARCH
switch archName {
case "x86_64":
archName = "amd64"
case "aarch64":
archName = "arm64"
}
ext := ".tar.gz"
if osName == "windows" {
ext = ".zip"
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://go.dev/VERSION?m=text", nil)
if err != nil {
return "", fmt.Errorf("create version request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("check go version: %w", err)
}
defer resp.Body.Close()
versionBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read go version: %w", err)
}
version := strings.TrimSpace(strings.Split(string(versionBytes), "\n")[0])
filename := fmt.Sprintf("%s.%s-%s%s", version, osName, archName, ext)
url := fmt.Sprintf("https://go.dev/dl/%s", filename)
// Fetch checksums from go.dev JSON API
checksums, err := fetchGoChecksums()
if err != nil {
return "", fmt.Errorf("fetch go checksums: %w", err)
}
expectedHash, ok := checksums[filename]
if !ok {
return "", fmt.Errorf("no checksum found for %s", filename)
}
log.Printf("[*] downloading %s ...", url)
tarball, err := os.CreateTemp("", "go-*"+ext)
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
defer os.Remove(tarball.Name())
dlCtx, dlCancel := context.WithTimeout(context.Background(), 120*time.Second)
defer dlCancel()
dlReq, err := http.NewRequestWithContext(dlCtx, "GET", url, nil)
if err != nil {
return "", fmt.Errorf("create download request: %w", err)
}
dlResp, err := http.DefaultClient.Do(dlReq)
if err != nil {
return "", fmt.Errorf("download go: %w", err)
}
defer dlResp.Body.Close()
hash := sha256.New()
if _, err := io.Copy(io.MultiWriter(tarball, hash), dlResp.Body); err != nil {
return "", fmt.Errorf("write go archive: %w", err)
}
tarball.Close()
got := hex.EncodeToString(hash.Sum(nil))
if got != expectedHash {
return "", fmt.Errorf("checksum mismatch for %s:\n expected: %s\n got: %s", filename, expectedHash, got)
}
log.Printf("[*] SHA256 verified: %s", expectedHash)
installDir := installGo(tarball.Name(), osName)
log.Printf("[+] Go %s installed at %s", version, installDir)
return filepath.Join(installDir, "bin", "go"), nil
}
func fetchGoChecksums() (map[string]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://go.dev/dl/?mode=json&include=all", nil)
if err != nil {
return nil, fmt.Errorf("create metadata request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("get go metadata: %w", err)
}
defer resp.Body.Close()
var versions []goVersion
if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {
return nil, fmt.Errorf("decode go metadata: %w", err)
}
checksums := make(map[string]string)
for _, v := range versions {
for _, f := range v.Files {
checksums[f.Filename] = f.SHA256
}
}
return checksums, nil
}
func installGo(archive, osName string) string {
homeDir, _ := os.UserHomeDir()
candidates := []string{
"/usr/local/go",
filepath.Join(homeDir, ".local", "go"),
"/tmp/go",
}
if osName == "windows" {
progFiles := os.Getenv("ProgramFiles")
if progFiles == "" {
progFiles = `C:\Program Files`
}
candidates = []string{
filepath.Join(progFiles, "Go"),
filepath.Join(homeDir, "sdk", "go"),
filepath.Join(os.TempDir(), "go"),
}
}
for _, dst := range candidates {
parent := filepath.Dir(dst)
os.RemoveAll(dst)
os.MkdirAll(parent, 0755)
if extractGoArchive(archive, parent, osName) == nil {
if _, err := os.Stat(dst); err == nil {
log.Printf("[*] Go extracted to %s", dst)
return dst
}
}
}
// Last resort: always try the root of the temp dir
tmpRoot := "/tmp"
if osName == "windows" {
tmpRoot = os.TempDir()
}
os.RemoveAll(filepath.Join(tmpRoot, "go"))
extractGoArchive(archive, tmpRoot, osName)
return filepath.Join(tmpRoot, "go")
}
func extractGoArchive(archive, dst, osName string) error {
if osName == "windows" {
return extractZip(archive, dst)
}
return exec.Command("tar", "-C", dst, "-xzf", archive).Run()
}
func extractZip(src, dst string) error {
r, err := zip.OpenReader(src)
if err != nil {
return fmt.Errorf("open zip: %w", err)
}
defer r.Close()
for _, f := range r.File {
target := filepath.Join(dst, f.Name)
// ZipSlip protection
if !strings.HasPrefix(filepath.Clean(target), filepath.Clean(dst)+string(os.PathSeparator)) {
continue
}
if f.FileInfo().IsDir() {
os.MkdirAll(target, 0755)
continue
}
os.MkdirAll(filepath.Dir(target), 0755)
rc, err := f.Open()
if err != nil {
return fmt.Errorf("open %s in zip: %w", f.Name, err)
}
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode())
if err != nil {
rc.Close()
return fmt.Errorf("create %s: %w", target, err)
}
_, err = io.Copy(out, rc)
rc.Close()
out.Close()
if err != nil {
return fmt.Errorf("extract %s: %w", f.Name, err)
}
}
return nil
}
func findGit(dir string) string {
gitExe := "git"
if runtime.GOOS == "windows" {
gitExe = "git.exe"
}
var found string
filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if !d.IsDir() && strings.EqualFold(d.Name(), gitExe) {
found = path
return io.EOF
}
return nil
})
return found
}
// auto-installs MinGit on Windows, tries system package manager on Linux/macOS.
// Self-contained implant generation is the feature — defer per-platform packaging.
func installGit() (string, error) {
homeDir, _ := os.UserHomeDir()
installDir := filepath.Join(homeDir, ".necropolis", "mingit")
if p := findGit(installDir); p != "" {
return p, nil
}
switch runtime.GOOS {
case "windows":
log.Printf("[*] downloading MinGit...")
url := "https://github.com/git-for-windows/git/releases/download/v2.47.0.windows.1/MinGit-2.47.0-64-bit.zip"
archive, err := os.CreateTemp("", "mingit-*.zip")
if err != nil {
return "", fmt.Errorf("create temp: %w", err)
}
defer os.Remove(archive.Name())
if err := downloadFile(url, archive); err != nil {
return "", fmt.Errorf("download mingit: %w", err)
}
archive.Close()
os.RemoveAll(installDir)
os.MkdirAll(installDir, 0755)
if err := extractZip(archive.Name(), installDir); err != nil {
return "", fmt.Errorf("extract mingit: %w", err)
}
if p := findGit(installDir); p != "" {
log.Printf("[+] MinGit installed at %s", p)
return p, nil
}
return "", fmt.Errorf("MinGit extracted but git executable not found under %s", installDir)
case "linux":
for _, pm := range [][]string{
{"apk", "add", "git"},
{"apt-get", "install", "-y", "git"},
{"yum", "install", "-y", "git"},
} {
if _, err := exec.LookPath(pm[0]); err != nil {
continue
}
cmd := exec.Command(pm[0], pm[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if cmd.Run() == nil {
if p, err := exec.LookPath("git"); err == nil {
return p, nil
}
}
}
return "", fmt.Errorf("could not install git via package manager; install git manually")
case "darwin":
for _, pm := range [][]string{
{"xcode-select", "--install"},
{"brew", "install", "git"},
} {
if _, err := exec.LookPath(pm[0]); err != nil {
continue
}
cmd := exec.Command(pm[0], pm[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if cmd.Run() == nil {
if p, err := exec.LookPath("git"); err == nil {
return p, nil
}
}
}
return "", fmt.Errorf("could not install git; install git manually")
default:
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
}
func downloadFile(url string, out *os.File) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
_, err = io.Copy(out, resp.Body)
return err
}