train-osource/pkg/installer/installer.go
wheelchairy 0fe28fc047 init
2025-02-04 15:49:54 +03:00

255 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package installer
import (
"archive/tar"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"train/pkg/manifest"
)
var (
binInstallDir = "./installed_bins" // for binary
buildInstallDir = "./installed_packages" // for build"
installDir = buildInstallDir // for func
)
func computeChecksum(filePath string) (string, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return "", err
}
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:]), nil
}
func downloadFile(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
tmpFile, err := ioutil.TempFile("", "package_")
if err != nil {
return "", err
}
_, err = io.Copy(tmpFile, resp.Body)
if err != nil {
return "", err
}
tmpFile.Close()
return tmpFile.Name(), nil
}
func unpackPackage(filePath, packageName string) error {
destDir := filepath.Join(buildInstallDir, packageName)
if err := os.MkdirAll(destDir, os.ModePerm); err != nil {
return err
}
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
gzr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
target := filepath.Join(destDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
return err
}
case tar.TypeReg:
outFile, err := os.Create(target)
if err != nil {
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return err
}
outFile.Close()
default:
}
}
return nil
}
func executeBuildCommands(commands []string, packageName string) error {
packagePath := filepath.Join(buildInstallDir, packageName)
for _, commandStr := range commands {
cmd := exec.Command("sh", "-c", commandStr)
cmd.Dir = packagePath
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("команда '%s' завершилась с ошибкой: %s", commandStr, string(output))
}
}
return nil
}
func saveInstalledPackage(name string, m *manifest.Manifest) error {
packagePath := filepath.Join(buildInstallDir, name)
if err := os.MkdirAll(packagePath, os.ModePerm); err != nil {
return err
}
jsonData, err := json.MarshalIndent(m, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(packagePath, "manifest.json"), jsonData, 0644)
}
func removeInstalledPackageRecord(name string) {
}
func InstallPackage(name string) error {
m, err := manifest.FetchManifest(name)
if err != nil {
return fmt.Errorf("не удалось получить манифест: %w", err)
}
if strings.ToLower(m.Mode) == "bin" {
platformKey := runtime.GOOS + "_" + runtime.GOARCH
binaryURL, ok := m.Binaries[platformKey]
if !ok {
return fmt.Errorf("нет бинарного файла для платформы %s", platformKey)
}
packageFile, err := downloadFile(binaryURL)
if err != nil {
return fmt.Errorf("не удалось скачать бинарник: %w", err)
}
defer os.Remove(packageFile)
checksum, err := computeChecksum(packageFile)
if err != nil {
return fmt.Errorf("не удалось вычислить контрольную сумму: %w", err)
}
if checksum != m.Checksum {
return fmt.Errorf("контрольная сумма не совпадает, ожидалось %s, получено %s", m.Checksum, checksum)
}
if err := os.MkdirAll(binInstallDir, os.ModePerm); err != nil {
return err
}
destPath := filepath.Join(binInstallDir, m.Name)
data, err := ioutil.ReadFile(packageFile)
if err != nil {
return err
}
if err := ioutil.WriteFile(destPath, data, 0755); err != nil {
return err
}
fmt.Printf("Бинарный пакет %s установлен в %s\n", m.Name, destPath)
return nil
} else if strings.ToLower(m.Mode) == "build" {
packageFile, err := downloadFile(m.Source)
if err != nil {
return fmt.Errorf("не удалось скачать пакет: %w", err)
}
defer os.Remove(packageFile)
checksum, err := computeChecksum(packageFile)
if err != nil {
return fmt.Errorf("не удалось вычислить контрольную сумму: %w", err)
}
if checksum != m.Checksum {
return fmt.Errorf("контрольная сумма не совпадает, ожидалось %s, получено %s", m.Checksum, checksum)
}
if err := unpackPackage(packageFile, name); err != nil {
return fmt.Errorf("не удалось распаковать пакет: %w", err)
}
if len(m.Build) > 0 {
if err := executeBuildCommands(m.Build, name); err != nil {
return fmt.Errorf("ошибка выполнения команд сборки: %w", err)
}
}
if err := saveInstalledPackage(name, m); err != nil {
return fmt.Errorf("не удалось сохранить информацию об установленном пакете: %w", err)
}
return nil
}
return errors.New("неизвестный режим пакета")
}
func RemovePackage(name string) error {
packagePath := filepath.Join(buildInstallDir, name)
if _, err := os.Stat(packagePath); os.IsNotExist(err) {
return errors.New("пакет не найден")
}
if err := os.RemoveAll(packagePath); err != nil {
return fmt.Errorf("не удалось удалить пакет: %w", err)
}
removeInstalledPackageRecord(name)
return nil
}
func UpdatePackage(name string) error {
if err := RemovePackage(name); err != nil {
return fmt.Errorf("не удалось удалить пакет для обновления: %w", err)
}
if err := InstallPackage(name); err != nil {
return fmt.Errorf("не удалось установить пакет: %w", err)
}
return nil
}
func ListInstalledPackages() ([]string, error) {
var pkgs []string
entries, err := ioutil.ReadDir(buildInstallDir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() {
pkgs = append(pkgs, entry.Name())
}
}
return pkgs, nil
}
func GetPackageInfo(name string) (string, error) {
infoFile := filepath.Join(buildInstallDir, name, "manifest.json")
data, err := ioutil.ReadFile(infoFile)
if err != nil {
return "", err
}
return string(data), nil
}