239 lines
6.5 KiB
Go
239 lines
6.5 KiB
Go
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/config"
|
||
"train/pkg/manifest"
|
||
"train/pkg/paths"
|
||
)
|
||
|
||
var (
|
||
binInstallDir = filepath.Join(paths.BaseDir(), "installed_bins")
|
||
buildInstallDir = filepath.Join(paths.BaseDir(), "installed_packages")
|
||
)
|
||
|
||
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()
|
||
}
|
||
}
|
||
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
|
||
} |